diff --git a/.eslintrc.json b/.eslintrc.json index 4370d227a..4c862c827 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -96,19 +96,23 @@ "autosize": false, "Avatar": true, "Avatars": true, - + "BlazeComponent": false, + "BlazeLayout": false, "CollectionHooks": false, + "DocHead": false, "ESSearchResults": false, "FastRender": false, "FlowRouter": false, "FS": false, "getSlug": false, "Migrations": false, + "moment": false, "Mousetrap": false, "Picker": false, "Presence": true, "presences": true, "Ps": true, + "ReactiveTabs": false, "Restivus": false, "SimpleSchema": false, "SubsManager": false, @@ -129,11 +133,13 @@ "CSSEvents": true, "EscapeActions": true, "Filter": true, + "Mixins": true, "Modal": true, "MultiSelection": true, "Popup": true, "Sidebar": true, "Utils": true, + "InlinedForm": true, "UnsavedEdits": true, "Notifications": true, "allowIsBoardAdmin": true, diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md new file mode 100644 index 000000000..07765d05f --- /dev/null +++ b/.github/ISSUE_TEMPLATE.md @@ -0,0 +1,56 @@ +## Issue + +UPGRADE: https://wekan.fi/upgrade/ + +Pull requests welcome to fix any broken links at docs directory, and organizing docs/Features and their screenshots to subdirectories of each feature. + +Please report these issues elsewhere: + +- SECURITY ISSUES, PGP EMAIL: https://github.com/wekan/wekan/blob/main/SECURITY.md +- UCS: https://github.com/wekan/univention/issues + +If WeKan Snap is slow, try this: https://github.com/wekan/wekan/wiki/Cron + +Please search existing Open and Closed issues, most questions have already been answered. + +If you can not login for any reason: https://github.com/wekan/wekan/wiki/Forgot-Password +Email settings, only SMTP MAIL_URL and MAIL_FROM are in use: +https://github.com/wekan/wekan/wiki/Troubleshooting-Mail + +### Server Setup Information + +Please anonymize info, and do not any of your Wekan board URLs, passwords, +API tokens etc to this public issue. + +* Did you test in newest Wekan?: +* Did you configure root-url correctly so Wekan cards open correctly (see https://github.com/wekan/wekan/wiki/Settings)? +* Operating System: +* Deployment Method (Snap/Docker/Sandstorm/bundle/source): +* Http frontend if any (Caddy, Nginx, Apache, see config examples from Wekan GitHub wiki first): +* Node.js Version: +* MongoDB Version: +* What webbrowser version are you using (Wekan should work on all modern browsers that support Javascript)? + +### Problem description + +Add a recorded animated gif (e.g. with https://github.com/phw/peek) about +how it works currently, and screenshot mockups how it should work. + + +#### Reproduction Steps + + + +#### Logs + +Check Right Click / Inspect / Console in you browser - generally Chromium +based browsers show more detailed info than Firefox based browsers. + +Please anonymize logs. + +Snap: sudo snap logs wekan.wekan + +Docker: sudo docker logs wekan-app + +If logs are very long, attach them in .zip file + diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml deleted file mode 100644 index 0d633d30f..000000000 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ /dev/null @@ -1,69 +0,0 @@ -name: 🐛 Bug Report -description: Report a bug to help improve WeKan -labels: ["bug"] -body: - - type: markdown - attributes: - value: | - Thanks for reporting a bug! Please ensure you are using the [latest version of WeKan](https://wekan.fi/install) and [Upgrade](https://wekan.fi/upgrade) before submitting. - - type: textarea - id: description - attributes: - label: Bug Description - description: A clear and concise description of what the bug is. Mention versions of WeKan, Node.js, database name and version, frontend webserver version like Caddy etc. - validations: - required: true - - type: dropdown - id: platform - attributes: - label: Platform / Installation Method - options: - - Snap Stable - - Snap Candidate - - Docker - - Sandstorm - - Source (Meteor) - - Windows - - Mac - - Other - validations: - required: true - - type: dropdown - id: CPU - attributes: - label: CPU - options: - - amd64 - - arm64 - - s390x - - Other - validations: - required: true - - type: textarea - id: reproduction - attributes: - label: Steps to Reproduce - description: How can we recreate this issue? - placeholder: | - 1. Go to '...' - 2. Click on '....' - 3. Scroll down to '....' - 4. See error - validations: - required: true - - type: textarea - id: logs - attributes: - label: Relevant Logs - description: | - - Please paste any relevant anonymized server logs or browser console errors here. - - Snap: sudo snap logs wekan.wekan - - Docker: sudo docker logs wekan-app - - If logs are very long, attach them in .zip file - render: shell - - type: textarea - id: context - attributes: - label: Additional Context - description: Add any other context, anonymized screenshots or GIF video about the bug, and screenshot mockups about how it should work. - diff --git a/.github/ISSUE_TEMPLATE/feature_request.yml b/.github/ISSUE_TEMPLATE/feature_request.yml deleted file mode 100644 index 99c26554c..000000000 --- a/.github/ISSUE_TEMPLATE/feature_request.yml +++ /dev/null @@ -1,31 +0,0 @@ -name: ✨ Feature Request -description: Suggest a new feature for WeKan -labels: ["Feature:Request"] -body: - - type: textarea - id: feature-description - attributes: - label: Problem Statement - description: Is your feature request related to a problem? Please describe. - placeholder: I'm always frustrated when [...] - validations: - required: true - - type: textarea - id: solution - attributes: - label: Proposed Solution - description: A clear and concise description of what you want to happen. - validations: - required: true - - type: textarea - id: alternatives - attributes: - label: Alternatives Considered - description: Any alternative solutions or features you've considered. - - type: textarea - id: context - attributes: - label: Additional Context - description: Add any other context, like anonymized screenshot mockups about how it should work. - - diff --git a/.github/ISSUE_TEMPLATE/security-report.yml b/.github/ISSUE_TEMPLATE/security-report.yml deleted file mode 100644 index fb4e5fbe6..000000000 --- a/.github/ISSUE_TEMPLATE/security-report.yml +++ /dev/null @@ -1,14 +0,0 @@ -name: 🛡️ Security Issue -description: Report a security vulnerability -labels: ["security", "critical"] -body: - - type: markdown - attributes: - value: | - ## ⚠️ IMPORTANT: Please do not report security vulnerabilities via public issues. - - To protect the WeKan community, we ask that you report security bugs privately. This allows us to fix the issue before it can be exploited by malicious actors. - - ### How to report: - Please read our **[Security Policy (SECURITY.md)](https://github.com/wekan/wekan/blob/main/SECURITY.md)** for the official reporting process and contact information. - diff --git a/.github/ISSUE_TEMPLATE/ucs-issue.yml b/.github/ISSUE_TEMPLATE/ucs-issue.yml deleted file mode 100644 index a8358360f..000000000 --- a/.github/ISSUE_TEMPLATE/ucs-issue.yml +++ /dev/null @@ -1,23 +0,0 @@ -name: 🗳️ Univention (UCS) Issue -description: Problems specifically related to the WeKan app on Univention Corporate Server -labels: ["UCS"] -body: - - type: markdown - attributes: - value: | - ## 🛑 Is this a UCS-specific issue? - - If your issue is related to the **Univention Corporate Server (UCS) integration**, packaging, or installation via the Univention App Center, it should be reported in the dedicated Univention repository. - - ### ➡️ [Report UCS Issues Here](https://github.com/wekan/univention/issues) - - --- - **Why?** - Reporting there ensures that the maintainers specifically focused on the UCS environment see your request. - - If you are certain this is a **core WeKan bug** that affects all platforms (Docker, Snap, etc.), please go back and use the standard [Bug Report](https://github.com/wekan/wekan/issues/new?template=bug-report.yml) template. - - type: textarea - id: ucs-details - attributes: - label: Brief Description (Optional) - description: If you still wish to post here, please provide a brief summary of why this is a core Wekan issue and not a UCS-specific integration bug. diff --git a/.github/workflows/depsreview.yaml b/.github/workflows/depsreview.yaml index ae5ae5989..8461b453c 100644 --- a/.github/workflows/depsreview.yaml +++ b/.github/workflows/depsreview.yaml @@ -9,6 +9,6 @@ jobs: runs-on: ubuntu-latest steps: - name: 'Checkout Repository' - uses: actions/checkout@v6 + uses: actions/checkout@v5 - name: 'Dependency Review' uses: actions/dependency-review-action@v4 diff --git a/.github/workflows/docker-publish.yml b/.github/workflows/docker-publish.yml index f01ba9dad..54af974ce 100644 --- a/.github/workflows/docker-publish.yml +++ b/.github/workflows/docker-publish.yml @@ -32,13 +32,13 @@ jobs: steps: - name: Checkout repository - uses: actions/checkout@v6 + uses: actions/checkout@v5 # Login against a Docker registry except on PR # https://github.com/docker/login-action - name: Log into registry ${{ env.REGISTRY }} if: github.event_name != 'pull_request' - uses: docker/login-action@b45d80f862d83dbcd57f89517bcf500b2ab88fb2 + uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef with: registry: ${{ env.REGISTRY }} username: ${{ github.actor }} @@ -48,14 +48,14 @@ jobs: # https://github.com/docker/metadata-action - name: Extract Docker metadata id: meta - uses: docker/metadata-action@030e881283bb7a6894de51c315a6bfe6a94e05cf + uses: docker/metadata-action@c1e51972afc2121e065aed6d45c65596fe445f3f with: images: ${{ env.REGISTRY }}/${{ env.IMAGE_NAME }} # Build and push Docker image with Buildx (don't push on PR) # https://github.com/docker/build-push-action - name: Build and push Docker image - uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294 + uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 with: context: . push: ${{ github.event_name != 'pull_request' }} diff --git a/.github/workflows/dockerimage.yml b/.github/workflows/dockerimage.yml index 0f85c0d96..14f8dfe01 100644 --- a/.github/workflows/dockerimage.yml +++ b/.github/workflows/dockerimage.yml @@ -15,6 +15,6 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v6 + - uses: actions/checkout@v5 - name: Build the Docker image run: docker build . --file Dockerfile --tag wekan:$(date +%s) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0c05c85e6..9d93b7588 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -15,7 +15,7 @@ jobs: runs-on: ubuntu-latest steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v5 with: fetch-depth: 0 diff --git a/.github/workflows/test_suite.yml b/.github/workflows/test_suite.yml index 8d401e56a..7e2e4944a 100644 --- a/.github/workflows/test_suite.yml +++ b/.github/workflows/test_suite.yml @@ -18,7 +18,7 @@ jobs: # runs-on: ubuntu-latest # steps: # - name: checkout -# uses: actions/checkout@v6 +# uses: actions/checkout@v5 # # - name: setup node # uses: actions/setup-node@v1 @@ -42,7 +42,7 @@ jobs: # needs: [lintcode] # steps: # - name: checkout -# uses: actions/checkout@v6 +# uses: actions/checkout@v5 # # - name: setup node # uses: actions/setup-node@v1 @@ -65,7 +65,7 @@ jobs: # needs: [lintcode,lintstyle] # steps: # - name: checkout -# uses: actions/checkout@v6 +# uses: actions/checkout@v5 # # - name: setup node # uses: actions/setup-node@v1 @@ -90,12 +90,12 @@ jobs: # CHECKOUTS - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v5 # CACHING - name: Install Meteor id: cache-meteor-install - uses: actions/cache@v5 + uses: actions/cache@v4 with: path: ~/.meteor key: v1-meteor-${{ hashFiles('.meteor/versions') }} @@ -104,7 +104,7 @@ jobs: - name: Cache NPM dependencies id: cache-meteor-npm - uses: actions/cache@v5 + uses: actions/cache@v4 with: path: ~/.npm key: v1-npm-${{ hashFiles('package-lock.json') }} @@ -113,7 +113,7 @@ jobs: - name: Cache Meteor build id: cache-meteor-build - uses: actions/cache@v5 + uses: actions/cache@v4 with: path: | .meteor/local/resolver-result-cache.json @@ -125,7 +125,7 @@ jobs: v1-meteor_build_cache- - name: Setup meteor - uses: meteorengineer/setup-meteor@v3 + uses: meteorengineer/setup-meteor@v2 with: meteor-release: '2.2' @@ -136,7 +136,7 @@ jobs: run: sh ./test-wekan.sh -cv - name: Upload coverage - uses: actions/upload-artifact@v7 + uses: actions/upload-artifact@v5 with: name: coverage-folder path: .coverage/ @@ -147,10 +147,10 @@ jobs: needs: [tests] steps: - name: Checkout - uses: actions/checkout@v6 + uses: actions/checkout@v5 - name: Download coverage - uses: actions/download-artifact@v8 + uses: actions/download-artifact@v6 with: name: coverage-folder path: .coverage/ diff --git a/.meteor/packages b/.meteor/packages index 4241bf770..e0baa96d9 100644 --- a/.meteor/packages +++ b/.meteor/packages @@ -16,13 +16,17 @@ es5-shim@4.8.0 # Collections aldeed:collection2 -reywood:publish-composite +cottz:publish-relations dburles:collection-helpers +idmontie:migrations +easy:search mongo@1.16.8 +mquandalle:collection-mutations # Account system accounts-password@2.4.0 useraccounts:core +useraccounts:flow-routing useraccounts:unstyled simple:rest-accounts-password wekan-ldap @@ -40,7 +44,11 @@ reactive-dict@1.3.1 session@1.2.1 tracker@1.3.3 underscore@1.0.13 +arillo:flow-router-helpers audit-argument-checks@1.0.7 +kadira:dochead +mquandalle:autofocus +ongoworks:speakingurl raix:handlebar-helpers http@2.0.0! # force new http package @@ -50,6 +58,10 @@ http@2.0.0! # force new http package # UI components ostrio:i18n reactive-var@1.0.12 +mousetrap:mousetrap +mquandalle:jquery-textcomplete +mquandalle:mousetrap-bindglobal +templates:tabs meteor-autosize shell-server@0.5.0 email@2.2.5 @@ -59,23 +71,26 @@ msavin:usercache meteorhacks:subs-manager meteorhacks:aggregate@1.3.0 wekan-markdown -quave:synced-cron +konecty:mongo-counter +percolate:synced-cron ostrio:cookies ostrio:files@2.3.0 +pascoual:pdfkit lmieulet:meteor-coverage meteortesting:mocha@2.0.3 aldeed:simple-schema matb33:collection-hooks simple:json-routes +kadira:flow-router spacebars service-configuration@1.3.2 communitypackages:picker minifier-css@1.6.4 blaze +kadira:blaze-layout +peerlibrary:blaze-components ejson@1.1.3 logging@1.3.3 wekan-fullcalendar -wekan-fontawesome - -useraccounts:flow-routing-extra -ostrio:flow-router-extra +momentjs:moment@2.29.3 +# wekan-fontawesome diff --git a/.meteor/release b/.meteor/release index 5152abe9d..c500c39d6 100644 --- a/.meteor/release +++ b/.meteor/release @@ -1 +1 @@ -METEOR@2.16 +METEOR@2.14 diff --git a/.meteor/versions b/.meteor/versions index eb3dfb3d9..886c8cb4a 100644 --- a/.meteor/versions +++ b/.meteor/versions @@ -1,5 +1,5 @@ -accounts-base@2.2.11 -accounts-oauth@1.4.4 +accounts-base@2.2.10 +accounts-oauth@1.4.3 accounts-password@2.4.0 aldeed:collection2@2.10.0 aldeed:collection2-core@1.2.0 @@ -7,6 +7,7 @@ aldeed:schema-deny@1.1.0 aldeed:schema-index@1.1.1 aldeed:simple-schema@1.5.4 allow-deny@1.1.1 +arillo:flow-router-helpers@0.5.2 audit-argument-checks@1.0.7 autoupdate@1.8.0 babel-compiler@7.10.5 @@ -19,25 +20,29 @@ boilerplate-generator@1.7.2 caching-compiler@1.2.2 caching-html-compiler@1.2.1 callback-hook@1.5.1 -check@1.4.1 +check@1.3.2 coffeescript@2.7.0 coffeescript-compiler@2.4.1 communitypackages:picker@1.1.1 +cottz:publish-relations@2.0.8 dburles:collection-helpers@1.1.0 ddp@1.4.1 -ddp-client@2.6.2 -ddp-common@1.4.1 +ddp-client@2.6.1 +ddp-common@1.4.0 ddp-rate-limiter@1.2.1 -ddp-server@2.7.1 +ddp-server@2.7.0 deps@1.0.12 diff-sequence@1.1.2 dynamic-import@0.7.3 +easy:search@2.2.1 +easysearch:components@2.2.2 +easysearch:core@2.2.2 ecmascript@0.16.8 ecmascript-runtime@0.8.1 ecmascript-runtime-client@0.12.1 ecmascript-runtime-server@0.11.0 ejson@1.1.3 -email@2.2.6 +email@2.2.5 es5-shim@4.8.0 fetch@0.1.4 geojson-utils@1.0.11 @@ -46,11 +51,16 @@ html-tools@1.1.3 htmljs@1.1.1 http@2.0.0 id-map@1.1.1 +idmontie:migrations@1.0.3 inter-process-messaging@0.1.1 jquery@3.0.0 +kadira:blaze-layout@2.3.0 +kadira:dochead@1.5.0 +kadira:flow-router@2.12.1 +konecty:mongo-counter@0.0.5_3 lmieulet:meteor-coverage@1.1.4 localstorage@1.2.0 -logging@1.3.4 +logging@1.3.3 matb33:collection-hooks@1.3.0 mdg:validation-error@0.5.1 meteor@1.11.5 @@ -64,32 +74,45 @@ meteortesting:browser-tests@1.4.2 meteortesting:mocha@2.1.0 meteortesting:mocha-core@8.0.1 minifier-css@1.6.4 -minifier-js@2.8.0 +minifier-js@2.7.5 minifiers@1.1.8-faster-rebuild.0 -minimongo@1.9.4 +minimongo@1.9.3 modern-browsers@0.1.10 modules@0.20.0 modules-runtime@0.13.1 -mongo@1.16.10 +momentjs:moment@2.29.3 +mongo@1.16.8 mongo-decimal@0.1.3 mongo-dev-server@1.1.0 mongo-id@1.0.8 mongo-livedata@1.0.12 +mousetrap:mousetrap@1.4.6_1 +mquandalle:autofocus@1.0.0 +mquandalle:collection-mutations@0.1.0 mquandalle:jade@0.4.9 mquandalle:jade-compiler@0.4.5 +mquandalle:jquery-textcomplete@0.8.0_1 +mquandalle:mousetrap-bindglobal@0.0.1 msavin:usercache@1.8.0 npm-mongo@4.17.2 oauth@2.2.1 oauth2@1.3.2 observe-sequence@1.0.21 +ongoworks:speakingurl@1.1.0 ordered-dict@1.1.0 ostrio:cookies@2.7.2 ostrio:cstorage@4.0.1 ostrio:files@2.3.3 -ostrio:flow-router-extra@3.10.1 ostrio:i18n@3.2.1 +pascoual:pdfkit@1.0.7 +peerlibrary:assert@0.3.0 +peerlibrary:base-component@0.17.1 +peerlibrary:blaze-components@0.23.0 +peerlibrary:computed-field@0.10.0 +peerlibrary:data-lookup@0.3.0 +peerlibrary:reactive-field@0.6.0 +percolate:synced-cron@1.5.2 promise@0.12.2 -quave:synced-cron@2.2.1 raix:eventemitter@0.1.3 raix:handlebar-helpers@0.2.5 random@1.2.1 @@ -99,9 +122,8 @@ reactive-dict@1.3.1 reactive-var@1.0.12 reload@1.3.1 retry@1.1.0 -reywood:publish-composite@1.9.0 routepolicy@1.1.1 -service-configuration@1.3.4 +service-configuration@1.3.3 session@1.2.1 sha@1.0.9 shell-server@0.5.0 @@ -114,6 +136,7 @@ socket-stream-client@0.5.2 spacebars@1.4.1 spacebars-compiler@1.3.1 standard-minifier-js@2.8.1 +templates:tabs@2.3.0 templating@1.4.1 templating-compiler@1.4.1 templating-runtime@1.5.0 @@ -121,20 +144,21 @@ templating-tools@1.2.2 tracker@1.3.3 typescript@4.9.5 ui@1.0.13 -underscore@1.6.1 +underscore@1.0.13 url@1.3.2 useraccounts:core@1.16.2 -useraccounts:flow-routing-extra@1.1.0 +useraccounts:flow-routing@1.15.0 useraccounts:unstyled@1.14.2 -webapp@1.13.8 +webapp@1.13.6 webapp-hashing@1.1.1 -wekan-accounts-cas@0.2.0 -wekan-accounts-lockout@1.1.0 +wekan-accounts-cas@0.1.0 +wekan-accounts-lockout@1.0.0 wekan-accounts-oidc@1.0.10 -wekan-accounts-sandstorm@0.9.0 -wekan-fontawesome@6.4.2 -wekan-fullcalendar@5.11.5 -wekan-ldap@0.1.0 +wekan-accounts-sandstorm@0.8.0 +wekan-fullcalendar@3.10.5 +wekan-ldap@0.0.2 wekan-markdown@1.0.9 -wekan-oidc@1.1.0 -zodern:types@1.0.13 +wekan-oidc@1.0.12 +yasaricli:slugify@0.0.7 +zimme:active-route@2.3.2 +zodern:types@1.0.10 diff --git a/.meteorignore b/.meteorignore deleted file mode 100644 index 49ec70b2d..000000000 --- a/.meteorignore +++ /dev/null @@ -1 +0,0 @@ -npm-packages/ diff --git a/.tx/config b/.tx/config index f6bdb4cda..7745b5608 100644 --- a/.tx/config +++ b/.tx/config @@ -1,6 +1,6 @@ [main] host = https://www.transifex.com -lang_map = te_IN: te-IN, es_AR: es-AR, es_419: es-LA, es_TX: es-TX, he_IL: he-IL, zh_CN: zh-CN, ar_EG: ar-EG, cs_CZ: cs-CZ, fa_IR: fa-IR, ms_MY: ms-MY, nl_NL: nl-NL, de_CH: de-CH, en_IT: en-IT, uz_UZ: uz-UZ, fr_CH: fr-CH, hi_IN: hi-IN, et_EE: et-EE, es_PE: es-PE, es_MX: es-MX, gl_ES: gl-ES, mn_MN: mn, zh_TW: zh-TW, ast_ES: ast-ES, es_CL: es-CL, ja_JP: ja, lv_LV: lv, ro_RO: ro-RO, az_AZ: az-AZ, cy_GB: cy-GB, gu_IN: gu-IN, pl_PL: pl-PL, vep: ve-PP, en_BR: en-BR, en@ysv: en-YS, hu_HU: hu, ko_KR: ko-KR, pt_BR: pt-BR, zh_HK: zh-HK, zu_ZA: zu-ZA, en_MY: en-MY, ja-Hira: ja-HI, fi_FI: fi, vec: ve-CC, vi_VN: vi-VN, fr_FR: fr-FR, id_ID: id, zh_Hans: zh-Hans, en_DE: en-DE, en_GB: en-GB, el_GR: el-GR, uk_UA: uk-UA, az@latin: az-LA, de_AT: de-AT, uz@Latn: uz-LA, vls: vl-SS, ar_DZ: ar-DZ, bg_BG: bg, es_PY: es-PY, fy_NL: fy-NL, uz@Arab: uz-AR, ru_UA: ru-UA, war: wa-RR, zh_CN.GB2312: zh-GB +lang_map = te_IN: te-IN, es_AR: es-AR, es_419: es-LA, es_TX: es-TX, he_IL: he-IL, zh_CN: zh-CN, ar_EG: ar-EG, cs_CZ: cs-CZ, fa_IR: fa-IR, ms_MY: ms-MY, nl_NL: nl-NL, de_CH: de-CH, en_IT: en-IT, uz_UZ: uz-UZ, fr_CH: fr-CH, hi_IN: hi-IN, et_EE: et-EE, es_PE: es-PE, es_MX: es-MX, gl_ES: gl-ES, mn_MN: mn, sl_SI: sl, zh_TW: zh-TW, ast_ES: ast-ES, es_CL: es-CL, ja_JP: ja, lv_LV: lv, ro_RO: ro-RO, az_AZ: az-AZ, cy_GB: cy-GB, gu_IN: gu-IN, pl_PL: pl-PL, vep: ve-PP, en_BR: en-BR, en@ysv: en-YS, hu_HU: hu, ko_KR: ko-KR, pt_BR: pt-BR, zh_HK: zh-HK, zu_ZA: zu-ZA, en_MY: en-MY, ja-Hira: ja-HI, fi_FI: fi, vec: ve-CC, vi_VN: vi-VN, fr_FR: fr-FR, id_ID: id, zh_Hans: zh-Hans, en_DE: en-DE, en_GB: en-GB, el_GR: el-GR, uk_UA: uk-UA, az@latin: az-LA, de_AT: de-AT, uz@Latn: uz-LA, vls: vl-SS, ar_DZ: ar-DZ, bg_BG: bg, es_PY: es-PY, fy_NL: fy-NL, uz@Arab: uz-AR, ru_UA: ru-UA, war: wa-RR, zh_CN.GB2312: zh-GB [o:wekan:p:wekan:r:application] file_filter = imports/i18n/data/.i18n.json diff --git a/.vscode/settings.json b/.vscode/settings.json index 860ffbbba..d5e338938 100644 --- a/.vscode/settings.json +++ b/.vscode/settings.json @@ -9,8 +9,5 @@ "TERM": "xterm-256color" }, "terminal.integrated.shell.linux": "/bin/bash", - "terminal.integrated.shellArgs.linux": [ - "-l" - ], - "files.simpleDialog.enable": true + "terminal.integrated.shellArgs.linux": ["-l"] } \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 0e87f58cf..4361f412b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,660 +8,23 @@ Newest WeKan at these platforms: - [Mac amd64, works also with Rosetta2 at Apple Silicon](https://github.com/wekan/wekan/blob/main/docs/Platforms/Propietary/Mac.md) - https://wekan.fi/install/ - Snap Candidate amd64 - - Docker amd64/arm64/s390x + - Docker amd64 - Kubernetes Docker amd64 - Bitnami MongoDB Docker images do not exist anymore. [MongoDump/MongoRestore to groundhog2k MongoDB images](https://github.com/wekan/charts/issues/45) Fixing other platforms In Progress. -- [Node.js 14.21.4](https://github.com/wekan/node-v14-esm/releases/tag/v14.21.4) or [Node.js 14.21.3](https://nodejs.org/dist/latest-v14.x/) -- MongoDB 6.x and 7.x, or [FerretDB2/PostgreSQL](https://github.com/wekan/wekan/blob/main/docs/Databases/FerretDB2-PostgreSQL.md) +- Node.js 14.x at https://github.com/wekan/node-v14-esm/releases/tag/v14.21.4 and https://nodejs.org/dist/latest-v14.x/ +- MongoDB 6.x and 7.x, or FerretDB/PostgreSQL https://blog.ferretdb.io/building-project-management-stack-wekan-ferretdb/ [Upgrade WeKan](https://wekan.fi/upgrade/) WeKan 8.00-8.06 had wrong raw database directory setting /var/snap/wekan/common/wekan and some cards were not visible. Those are fixed at WeKan 8.07 where database directory is back to /var/snap/wekan/common and all cards are visible. -WeKan 8.00-8.24 used Colorful Unicode Emoji Icons, versions before and after use mostly Font Awesome 4.7 icons. - -Upgrading to Meteor 3 progress: - -- https://harryadel.com/dev-diary-24/ -- https://harryadel.com/dev-diary-25/ - -# Upcoming WeKan ® release - -This release adds the following updates: - -- [Bump docker/build-push-action from 6.19.2 to 7.0.0](https://github.com/wekan/wekan/pull/6181). - Thanks to dependabot. -- [Bump docker/metadata-action from 5.10.0 to 6.0.0](https://github.com/wekan/wekan/pull/6182). - Thanks to dependabot. -- [Bump docker/login-action from 3.7.0 to 4.0.0](https://github.com/wekan/wekan/pull/6183). - Thanks to dependabot. -- [Remove peerlibrary:blaze-components](https://github.com/wekan/wekan/pull/6178). - Thanks to harryadel. - -and fixes the following bugs: - -- [Fixed linked card swimlane routing](https://github.com/wekan/wekan/pull/6179). - Thanks to KhaoulaMaleh. -- [Replaced incompatible file-type with mime-type](https://github.com/wekan/wekan/commit/89f86caf69db0600a207aee075361f8a6801253b). - Thanks to xet7. -- [Fix Add List popup to not open after adding new board or there being no lists at all](https://github.com/wekan/wekan/commit/7e378be1d87280b8fb3f63eea3c0374e12054984). - Thanks to xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.35 2026-03-05 WeKan ® release - -This release adds the following CRITICAL SECURITY FIXES of [IntegrationBleed](https://wekan.fi/hall-of-fame/integrationBleed/): - -- [Fix IntegrationBleed](https://github.com/wekan/wekan/commit/2cd702f48df2b8aef0e7381685f8e089986a18a4). - Thanks to Rodolphe GHIO and xet7. - -and adds the following updates: - -- [Bump minimatch from 3.1.3 to 3.1.5](https://github.com/wekan/wekan/pull/6167). - Thanks to dependabot. -- [Bump actions/download-artifact from 7 to 8](https://github.com/wekan/wekan/pull/6170). - Thanks to dependabot. -- [Bump meteorengineer/setup-meteor from 2 to 3](https://github.com/wekan/wekan/pull/6171). - Thanks to dependabot. -- [Bump actions/upload-artifact from 6 to 7](https://github.com/wekan/wekan/pull/6172). - Thanks to dependabot. -- [Replace konecty:mongo-counter with inline implementation](https://github.com/wekan/wekan/pull/6174). - Thanks to harryadel. -- [Replace templates:tabs package with inline Blaze implementation](https://github.com/wekan/wekan/pull/6175). - Thanks to harryadel. -- [Updated dompurify](https://github.com/wekan/wekan/commit/9f79a8b6edc161f95c7362f45597b8c6ec777088). - Thanks to xet7. - -and fixes the following bugs: - -- [Commented out Admin Panel/Settings/Migrations related menu option and code to speed up WeKan](https://github.com/wekan/wekan/commit/9b3ecd795fffaf012911d0d36cea0ee362e2fc27). - Thanks to xet7. -- [Optimized board loading](https://github.com/wekan/wekan/commit/7127862bea34ab84ebf8ef00727e3f7633ca8b69). - Thanks to xet7. -- [Fix FilesCollection findOneAsync errors for Avatars and Attachments](https://github.com/wekan/wekan/pull/6173). - Thanks to harryadel. -- [Fixed unable to delete Avatar, with Meteor 3 compatible avatar and attachments fixes](https://github.com/wekan/wekan/commit/274f1309c389221915b40508faffdfc361d48bbf). - Thanks to inDane and xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.34 2026-02-20 WeKan ® release - -This release adds the following CRITICAL SECURITY FIXES of [AnchorBleed](https://wekan.fi/hall-of-fame/anchorBleed/): - -- [Fix GHSL-2026-035_Wekan CursorBleed of AnchorBleed](https://github.com/wekan/wekan/commit/1c8667eae8b28739e43569b612ffdb2693c6b1ce). - Thanks to GHSL and xet7. -- [Fix GHSL-2026-036_Wekan WatchBleed of AnchorBleed](https://github.com/wekan/wekan/commit/8c00adc6b865653bd717a946dd646eb54ac78c9c). - Thanks to GHSL and xet7. -- [Fix GHSL-2026-037_Wekan GlobalBleed of AnchorBleed](https://github.com/wekan/wekan/commit/1ee9b2e917104f54c035f6426169a28fedecbdb6). - Thanks to GHSL and xet7. -- [Fix GHSL-2026-044_Wekan CustomFieldBleed of AnchorBleed](https://github.com/wekan/wekan/commit/73eb98c57afd3d72377a1f7160a52450ab0eeb8b). - Thanks to GHSL and xet7. -- [Fix GHSL-2026-045_Wekan ImportBleed of AnchorBleed](https://github.com/wekan/wekan/commit/62216e36c15f55d4ef6cb97313db3aa54fc77fe0). - Thanks to GHSL and xet7. - -and adds the following new features: - -- [Helm Chart: Feat(ingress): add ingressClassName support](https://github.com/wekan/charts/pull/50). - Thanks to Rohmilchkaese. - -and adds the following updates: - -- [Migrate @wekanteam/meteor-reactive-cache](https://github.com/wekan/wekan/pull/6139). - Thanks to harryadel. -- [Fix unhandled Promise rejection in cron migration job callback](https://github.com/wekan/wekan/pull/6153). - Thanks to harryadel. -- [Bump docker/build-push-action from 6.18.0 to 6.19.2](https://github.com/wekan/wekan/pull/6149). - Thanks to dependabot. -- [Bump ajv from 6.12.6 to 8.18.0](https://github.com/wekan/wekan/pull/6151). - Thanks to dependabot. -- [Bump tar from 7.5.7 to 7.5.9](https://github.com/wekan/wekan/pull/6156). - Thanks to dependabot. -- [Updated dependencies](https://github.com/wekan/wekan/commit/f463198e40f9802c0a30f2d713d831e905678162). - Thanks to developers of dependencies. -- [Moved meteor-reactive-cache to npmjs.com @wekanteam/meteor-reactive-cache https://github.com/wekan/meteor-reactive-cache](https://github.com/wekan/wekan/commit/8816c886cf740ec43c4c00c946730e6c2f3a8237). - Thanks to xet7. - -and fixes the following bugs: - -- [Fix calendar](https://github.com/wekan/wekan/pull/6155). - Thanks to KhaoulaMaleh. -- [Removed duplicate code](https://github.com/wekan/wekan/commit/ed907f8c61f59763a87cc738f94bff418de77701). - Thanks to xet7. -- [Fix createWorkspace Meteor method fails with "Expected string, got undefined"](https://github.com/wekan/wekan/commit/06d418b12b5de6392dab12c2d3b262813b92e730). - Thanks to TheBoysenBuilds and xet7. -- [Fix Notifications from not allowed Boards](https://github.com/wekan/wekan/commit/a34c2f35a6c4ae64b97af0a930fb768b2d781938). - Thanks to FK-PATZ3 and xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.33 2026-02-15 WeKan ® release - -This release adds the following new features: - -- [Admin Panel/Settings/Layout, for PWA: Custom head meta, link, icons, assetlinks.json, site.webmanifest](https://github.com/wekan/wekan/commit/b5a13f0206ff9b44329a1cf8d4f2b84ca1c7bd91). - Thanks to xet7. - -and adds the following updates: - -- [Migrate wekan-ldap to async API for Meteor 3.0](https://github.com/wekan/wekan/pull/6115). - Thanks to harryadel. -- [Updated dependencies](https://github.com/wekan/wekan/commit/bebea9efeab098f7f5faca3f75019fd9efbcb5ac). - Thanks to developers of dependencies. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.32 2026-02-13 WeKan ® release - -This release adds the following updates: - -- [Migrate wekan-oidc to async API for Meteor 3.0](https://github.com/wekan/wekan/pull/6111). - Thanks to harryadel. -- [Migrate wekan-accounts-sandstorm to async API for Meteor 3.0](https://github.com/wekan/wekan/pull/6112). - Thanks to harryadel. -- [Migrate wekan-accounts-cas to async API for Meteor 3.0](https://github.com/wekan/wekan/pull/6114). - Thanks to harryadel. -- [Updated to MongoDB 7.0.30 at Snap Candidate](https://github.com/wekan/wekan/commit/fed2e9dd4e3c571795af24f60c6643a33bb5ecf9). - Thanks to MongoDB developers. -- [Updated MongoDB to 7.0.30 at Helm Chart](https://github.com/wekan/wekan/commit/commit/98f66a2b92f7a2c199135e8239133ef431c332b9). - Thanks to MongoDB developers. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.31 2026-02-08 WeKan ® release - -This release fixes the following bugs: - -- [Fix Copy Card and Move Card](https://github.com/wekan/wekan/commit/f8aa487e9118264f4d96c4d0cde384bcaf05e0a0). - Thanks to xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.30 2026-02-08 WeKan ® release - -This release reverts the following new features and adds the following fixes: - -- [Reverted New UI Design of WeKan v8.29 and added more fixes and performance improvements](https://github.com/wekan/wekan/commit/1b8b8d2eef5b56654026597ae445f3f20ad886b2). - Thanks to xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.29 2026-02-07 WeKan ® release - -This release adds the following new features: - -- New UI Design. - [Part 1](https://github.com/wekan/wekan/pull/6131), - [Part 2](https://github.com/wekan/wekan/pull/6133). - Thanks to Chostakovitch. - -and fixes the following bugs: - -- [Fix List widths](https://github.com/wekan/wekan/pull/6129). - Thanks to KhaoulaMaleh. -- [Fix extra space at RTL need margin](https://github.com/wekan/wekan/commit/4456bc13609b2d0e944ee71a82df200060a601b2). - Thanks to mimZD and xet7. -- [Fix No Add Card + etc](https://github.com/wekan/wekan/commit/55710835fe8879775b73c8bc921bac5febf552a2). - Thanks to mimZD and xet7. -- [Removed extra file](https://github.com/wekan/wekan/commit/0987154a7fea89b0416f48d9bffd5fa7fba9908a). - Thanks to xet7. -- [Added missing linefeeds](https://github.com/wekan/wekan/commit/0ae9865fcbad42966988225393fa66bca49cf14e). - Thanks to xet7. -- [Fix Notifications from not allowed Boards](https://github.com/wekan/wekan/commit/0a92e896f8d2cf0677891857d163ada336a45c61). - Thanks to FK-PATZ3 and xet7. -- [Fix move and copy popup duplicate view](https://github.com/wekan/wekan/commit/631c250f403172937b76ddd37bab54bc9b6dbb78). - Thanks to mimZD and xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.28 2026-02-05 WeKan ® release - -This release adds the following updates: - -- [Bump docker/login-action from 3.6.0 to 3.7.0](https://github.com/wekan/wekan/pull/6122). - Thanks to dependabot. -- [Updated meteor-node-stubs](https://github.com/wekan/wekan/commit/6c2e2f271d6343b347224430a4eedfe54db2d838). - Thanks to Meteor developers. - -and fixes the following bugs: - -- [Fixed text truncation at quick-access board link bar](https://github.com/wekan/wekan/pull/6121). - Thanks to KhaoulaMaleh. -- [Improved cardDetails.css for better UI](https://github.com/wekan/wekan/pull/6124). - Thanks to AymenHassini19. -- [Fixed Jade syntax at header](https://github.com/wekan/wekan/commit/c31758960f5372e88f47e8d081404294751284c8). - Thanks to xet7. -- [Await async setDone before closing popup in copy/move dialogs](https://github.com/wekan/wekan/pull/6126). - Thanks to harryadel. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.27 2026-01-31 WeKan ® release - -This release adds the following updates: - -- [Updated MongoDB to 7.0.29 at Windows install docs](https://github.com/wekan/wekan/commit/b55e1bbd409f76bd0388d19d4d0a8420cee8df96). - Thanks to MongoDB developers. - -and fixes the following bugs: - -- [Fix async/await in copy/move card operations](https://github.com/wekan/wekan/pull/6120). - Thanks to harryadel. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.26 2026-01-31 WeKan ® release - -This release adds the following updates: - -- [Migrate wekan-accounts-lockout to async API for Meteor 3.0](https://github.com/wekan/wekan/pull/6113). - Thanks to harryadel. -- Added Docs: Spreadsheet vs Kanban. - [Part 1](https://github.com/wekan/wekan/commit/a0a8d0186cbc7fefe38f72244723bcff292ae2f4), - [Part 2](https://github.com/wekan/wekan/commit/37d0daee590ab48cbfa1672e4bc5efd95d341211). - Thanks to xet7. -- [Updated dependencies](https://github.com/wekan/wekan/commit/03439d1bccf82511870eed7301b621b1d495941b). - Thanks to developers of dependencies. - -and fixes the following bugs: - -- [Reduce visual overflow in Member Settings menu by extending container height](https://github.com/wekan/wekan/pull/6104). - Thanks to AymenHassini19. -- [Fix Card copy menu is not displayed](https://github.com/wekan/wekan/commit/0b891464b907b272e075d8aafd3ce29e704739cf). - Thanks to xet7. -- [Fix Bug: Rules view translation not is not shown correctly](https://github.com/wekan/wekan/commit/f73eab23f997efe5347aa1f06515bf355cfe7ed5). - Thanks to cactus7as and xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.25 2026-01-28 WeKan ® release - -This release fixes the following CRITICAL SECURITY ISSUES of [FloppyBleed](https://wekan.fi/hall-of-fame/floppyBleed/): - -- [Fix FileBleed of FloppyBleed](https://github.com/wekan/wekan/commit/a419d831a408f251c798f5410375b20afd98c04b). - Thanks to Luke Hebenstreit Twitter lheben_ and xet7. - -and adds the following updates: - -- [Updated code counts](https://github.com/wekan/wekan/commit/2f25f47d0ba4c7f543264cd7fe2ed117ab0ec9ee). - Thanks to xet7. -- Updated FerretDB 2 / PostgreSQL docs location. - [Part 1](https://github.com/wekan/wekan/commit/710d522e069b7521b6c2ec4f93f1491a897cf2b4), - [Part 2](https://github.com/wekan/wekan/commit/0ede9d6d93a688f24fc36c0c456e184a0aa6af8c), - [Part 3](https://github.com/wekan/wekan/commit/bf5d50e8a9fce327a16b069932fa3e13c6d81978). - Thanks to xet7. -- [Updated Dockerfile](https://github.com/wekan/wekan/commit/d298ab7486d489d353fc410232a9dcdd68501c72). - Thanks to xet7. -- Docker for Linux amd64/arm64/s390x. - [Part 1](https://github.com/wekan/wekan/commit/38711f0a29bf37d1e0a3fd9c8a9bcfb2442934b3), - [Part 2](https://github.com/wekan/wekan/commit/e72019fa55ef6142767fd83e928bf2a0a966f9e6), - [Part 3](https://github.com/wekan/wekan/commit/b2c7c7f55b5136bc91251cd57125316ec622d4a3), - [Part 4](https://github.com/wekan/wekan/commit/98e5adfba80ee935b2a1293851d88812ad707b78), - [Part 5](https://github.com/wekan/wekan/commit/60846a44959d46262672c6a3048bd76d829c03bf), - [Part 6](https://github.com/wekan/wekan/commit/7ff174cf660f43dfbb471b29d75820f527771bbd). - Thanks to xet7. -- [Most Unicode Icons back to Font Awesome 4.7 for better accessibility. Less always visible buttons, More at ☰ Menu](https://github.com/wekan/wekan/commit/7ad04f45353e1628881fec310caedf7625a34d4d). - Thanks to xet7. -- [Updated to MongoDB 7.0.29 at Snap Candidate](https://github.com/wekan/wekan/commit/ac70fe28488c09364133a65fbc80f5a819a1e4bf). - Thanks to developers of MongoDB. -- [Updated to MongoDB 7.0.29 at Helm Charts](https://github.com/wekan/charts/commit/8169739260b6f104c4d011dac5a4bf5485db8b45). - Thanks to developers of MongoDB. - -and fixes the following bugs: - -- [Fix autofocus](https://github.com/wekan/wekan/commit/440f553de0baf460acc891ee5864f84bb982104a). - Thanks to xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.24 2026-01-24 WeKan ® release - -This release adds the following updates: - -- Secure Sandbox for VSCode at Debian 13 amd64. - [Part 1](https://github.com/wekan/wekan/commit/639ac9549f88069d8569de777c533ab4c9438088), - [Part 2](https://github.com/wekan/wekan/commit/cc8b771eb448199fa23a87955cf9fa1a504ba8d2). - Thanks to xet7. -- [Updated build scripts and docs to Meteor 2.16](https://github.com/wekan/wekan/commit/1d374db0f3ed35a0463b5f89ca2d01078e245d11). - Thanks to xet7. -- [Replace mquandalle:collection-mutations with collection helpers](https://github.com/wekan/wekan/pull/6086). - Thanks to harryadel. -- [Replace ongoworks:speakingurl with limax](https://github.com/wekan/wekan/pull/6087). - Thanks to harryadel. -- [Migrate createIndex to createIndexAsync](https://github.com/wekan/wekan/pull/6093). - Thanks to harryadel. -- [Remove idmontie:migrations](https://github.com/wekan/wekan/pull/6095). - Thanks to harryadel. -- Remove mquandalle:autofocus. - [Part 1](https://github.com/wekan/wekan/pull/6088), - [Part 2](https://github.com/wekan/wekan/pull/6096). - Thanks to harryadel. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.23 2026-01-21 WeKan ® release - -This release adds the following updates: - -- [Migrate from percolate:synced-cron to quave:synced-cron](https://github.com/wekan/wekan/pull/6080). - Thanks to harryadel. -- [Replace mousetrap](https://github.com/wekan/wekan/pull/6082). - Thanks to harryadel. -- [Remove kadira:dochead](https://github.com/wekan/wekan/pull/6083). - Thanks to harryadel. -- [Replace cottz:publish-relations with reywood:publish-composite](https://github.com/wekan/wekan/pull/6084). - Thanks to harryadel. -- [Bump tar from 7.5.3 to 7.5.6](https://github.com/wekan/wekan/pull/6085). - Thanks to dependabot. -- [Updated dependencies](https://github.com/wekan/wekan/commit/04bfa0e6ba278a9d6544a678d1fba3ea71841062). - Thanks to developers of dependencies. - -and fixes the following bugs: - -- [Fixed newly created "Default" swimlane are displayed as "key 'default (LOCALE)' returned an object instead of string"](https://github.com/wekan/wekan/commit/ce55f0d8f432922ca4c0e3d28b1fb0e826d8008f). - Thanks to brlin-tw and xet7. -- [Fix DB migration from 8.19 to 8.21 stuck forever](https://github.com/wekan/wekan/commit/a31a615da6911a2db22d4db86875b31fc951ae96). - Thanks to MaccabeeY and xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.22 2026-01-20 WeKan ® release - -This release fixes the following bugs: - -- [Fixed Add member and @mentions](https://github.com/wekan/wekan/commit/ad511bd1378afdca7264597900a11ab6b5e09b77). - Thanks to xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.21 2026-01-18 WeKan ® release - -This release fixes the following CRITICAL SECURITY ISSUES of [SnowBleed](https://wekan.fi/hall-of-fame/snowBleed/): - -- [Security Fix 2: OrgsTeamsBleed](https://github.com/wekan/wekan/commit/cabfeed9a68e21c469bf206d8655941444b9912c). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 3: ChecklistRESTBleed](https://github.com/wekan/wekan/commit/251d49eea94834cf351bb395808f4a56fb4dbb44). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 4: MigrationsBleed2](https://github.com/wekan/wekan/commit/cc35dafef57ef6e44a514a523f9a8d891e74ad8f). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 5: PositionHistoryBleed](https://github.com/wekan/wekan/commit/55576ec17722db094835470b386162c9a662fb60). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 6: SyncLDAPBleed](https://github.com/wekan/wekan/commit/146905a459106b5d00b4f09453a6554255e6965a). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 7: AttachmentMigrationBleed](https://github.com/wekan/wekan/commit/053bf1dfb76ef230db162c64a6ed50ebedf67eee). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 8: MoveStorageBleed](https://github.com/wekan/wekan/commit/c413a7e860bc4d93fe2adcf82516228570bf382d). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 9: ListWIPBleed](https://github.com/wekan/wekan/commit/8c0b4f79d8582932528ec2fdf2a4487c86770fb9). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 10: BoardTitleRESTBleed](https://github.com/wekan/wekan/commit/545566f5663545d16174e0f2399f231aa693ab6e). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 11: CardPubSubBleed](https://github.com/wekan/wekan/commit/0f5a9c38778ca550cbab6c5093470e1e90cb837f). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 12: FixDuplicateBleed](https://github.com/wekan/wekan/commit/4ce181d17249778094f73d21515f7f863f554743). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 13: LinkedBoardActivitiesBleed](https://github.com/wekan/wekan/commit/91a936e07d2976d4246dfe834281c3aaa87f9503). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 14: RulesBleed](https://github.com/wekan/wekan/commit/a787bcddf33ca28afb13ff5ea9a4cb92dceac005). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. - -and adds the following new features: - -- [Show password at Login and Register pages](https://github.com/wekan/wekan/commit/d30192f7f925a055e6f31723c47ad32b628ff2c0). - Thanks to xet7. - -and adds the following updates: - -- [Updated Docker build command](https://github.com/wekan/wekan/commit/b88b27689af8c5abf23dd7891780581a2d92001d). - Thanks to xet7. -- [Updated Windows Bundle build .bat script](https://github.com/wekan/wekan/commit/f0118d52e984628b0e06e36d7b7f90166d18fbf7). - Thanks to xet7. -- [Updated Linux arm64 bundle build script](https://github.com/wekan/wekan/commit/e2ec50730ff7fd4eb805071bb17fe0c105514f83). - Thanks to xet7. -- [Updated Linux s390x bundle build script](https://github.com/wekan/wekan/commit/980510d71ad428325645dd53297f4ce20bd12983). - Thanks to xet7. -- [Bump tar and @mapbox/node-pre-gyp](https://github.com/wekan/wekan/pull/6071). - Thanks to dependabot. -- [Upgrade to Meteor 2.16](https://github.com/wekan/wekan/pull/6072). - Thanks to harryadel. -- [Updated dependencies](https://github.com/wekan/wekan/commit/95da8966fe3bebc7c5ef2c1fc555de5fa239f8ca). - Thanks to developers of dependencies. - -and fixes the following bugs: - -- [Fixed "Copy card link to clipboard" icon often not working](https://github.com/wekan/wekan/commit/d337afd5d3e8ca719adcde13d2b24d983e0f9926). - Thanks to brlin-tw and xet7. -- [Fix DB migration from 8.19 to 8.20 is in a loop](https://github.com/wekan/wekan/commit/2fa490d83da858b193ca6a363e1599c5bbe55640). - Thanks to MaccabeeY and xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.20 2026-01-16 WeKan ® release - -This release fixes the following CRITICAL SECURITY ISSUES of [SnowBleed](https://wekan.fi/hall-of-fame/snowBleed/): - -- [Security Fix 1: MigrationsBleed](https://github.com/wekan/wekan/commit/cbb1cd78de3e40264a5e047ace0ce27f8635b4e6). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. - -and adds the following features: - -- [Added back feature: Toggle Drag Handles. Improved positions of Add List etc buttons](https://github.com/wekan/wekan/commit/5cb712bee4cf46c6fe13d7dacf4b62298152b894). - Thanks to xet7. - -and adds the following updates: - -- [Updated dependencies](https://github.com/wekan/wekan/pull/6059). - Thanks to dependabot. -- [Updated dependencies and published as @wekanteam npm packages to npmjs.com](https://github.com/wekan/wekan/commit/a9a89b501a91ffcdbdd611a05029d9483c59e4db). - Thanks to xet7. -- Added FerretDB2/PostgreSQL Docs. - [Part 1](https://github.com/wekan/wekan/commit/9fb1aeb8272b011c3d0b6b2c26ff7cb498c7b37f), - [Part 2](https://github.com/wekan/wekan/commit/f198421f10dd3be9d58f64a242d12ea1ef45fee3), - [Part 3](https://github.com/wekan/wekan/commit/9431b2d53014289bebb06567f5662fdcb6dd409c), - [Part 4](https://github.com/wekan/wekan/commit/ffd37b9fd9171ca22973d6d0a62baef4a18494f5). - Thanks to juri_ at WeKan Libera.Chat IRC and xet7. -- [Added s390x firewall Docs](https://github.com/wekan/wekan/commit/ec7c0e6dc3641f43b1a110d285f6ef15c146584a). - Thanks to xet7. -- Updated GitHub issue templates. - [Part 1](https://github.com/wekan/wekan/commit/bd37b88e4d508c1f2712184a27dbbfd9df0e4c4e), - [Part 2](https://github.com/wekan/wekan/commit/cf6e6914989a7bf1d79f8b753a0a576c54ad7580), - [Part 3](https://github.com/wekan/wekan/commit/4a658dc02a770f8219669dc10bfe1077c760744f). - Thanks to xet7. -- [Migrate kadira:flow-router to ostrio:flow-router-extra](https://github.com/wekan/wekan/pull/6067), related to Meteor 3 upgrades. - Thanks to harryadel. -- [Some fixes to make WeKan working after Meteor 3 related router upgrades](https://github.com/wekan/wekan/commit/984a2dcec18fd20ebd1a5add8380d4c13d8303ba). - Thanks to xet7. - -and fixes the following bugs: - -- [Fix attachment download error with non-ASCII filenames](https://github.com/wekan/wekan/pull/6056). - Thanks to brlin-tw. -- [Swimlane drag button position improvements](https://github.com/wekan/wekan/commit/376a30f8a9c5cc6b5341fda7336244ee1b9983fd). - Thanks to TDSCDMA and xet7. -- [Removed extra list borders](https://github.com/wekan/wekan/commit/a4f8faa48e3fb6c617cf9c5a398bc7f85b8bae92). - Thanks to TDSCDMA and xet7. -- [Add back button texts to Filter, Search, Board View and MultiSelection](https://github.com/wekan/wekan/commit/dac7e17500de97febc7ad8f84cd1bf5edab27c52). - Thanks to audiocrush and xet7. -- [Removed extra pipe character from UI](https://github.com/wekan/wekan/commit/66e79d2df7ecf5526dbae360cf93352657db7fcf). - Thanks to xet7. -- [Changed find.sh to not search from translations, because I'm trying to find code, not translations](https://github.com/wekan/wekan/commit/58ae2b6c6848235132308611fe3083533e120f72). - Thanks to xet7. -- [Fixed Change Avatar. Improved Admin Panel: People columns order, selected tab background color. Fixed can not edit existing user at Admin Panel/People/People](https://github.com/wekan/wekan/commit/07186e12a93c56555feb3b7332d43a918abe7f20). - Thanks to xet7. -- [Fix mentions and notifications drawer](https://github.com/wekan/wekan/commit/20b5e2ab8fd37303cda8305d87d757c1cb9bdd12). - Thanks to xet7. -- Fix New Board Permissions: NormalAssignedOnly, CommentAssignedOnly, ReadOnly, ReadAssignedOnly. - [Part 1](https://github.com/wekan/wekan/commit/eabb6a239d20530f538d22f94d9cfbebeb847493). - Thanks to nazim-oss and xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.19 2025-12-29 WeKan ® release - -This release fixes the following CRITICAL SECURITY ISSUES of [MegaBleed](https://wekan.fi/hall-of-fame/megaBleed/): - -- [Security Fix 1: IDOR in setCreateTranslation. Non-admin could change Custom Translation](https://github.com/wekan/wekan/commit/f244a43771f6ebf40218b83b9f46dba6b940d7de). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 2: Private-only board setting can be bypassed](https://github.com/wekan/wekan/commit/7ed76c180ede46ab1dac6b8ad27e9128a272c2c8). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 3: Card comment author spoofing (IDOR) via API](https://github.com/wekan/wekan/commit/67cb47173c1a152d9eaf5469740992b2dacdf62d). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 4: Cross-board card move without destination authorization](https://github.com/wekan/wekan/commit/198509e7600981400353aec6259247b3c04e043e). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 5: Read-only roles can still update cards](https://github.com/wekan/wekan/commit/181f837d8cbae96bdf9dcbd31beaa3653c2c0285). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 6: Checklist delete IDOR: checklist not verified against board/card](https://github.com/wekan/wekan/commit/08a6f084eba09487743a7c807fb4a9000fcfa9ac). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 7: Checklist create IDOR: cardId not verified against boardId](https://github.com/wekan/wekan/commit/5cd875813fdec5a3c40a0358b30a347967c85c14). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 8: Attachments publication leaks metadata without auth](https://github.com/wekan/wekan/commit/6dfa3beb2b6ab23438d0f4395b84bf0749eb4820). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 9: Attachment upload not scoped to card/board relationship](https://github.com/wekan/wekan/commit/1d16955b6d4f0a0282e89c2c1b0415c7597019b8). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. -- [Security Fix 10: LDAP filter injection in LDAP auth](https://github.com/wekan/wekan/commit/0b0e16c3eae28bbf453d33a81a9c58ce7db6d5bb). - Thanks to [Joshua Rogers](https://joshua.hu) of [Aisle Research](https://aisle.com) and xet7. - -and adds the following new features: - -- [Opened card Checklist menu: Hide finished tasks. Show Checklist at Minicard](https://github.com/wekan/wekan/commit/fbfde81bc8208b718c070a6eeba4b2e2d2ce83ba). - Thanks to C0rn3j and xet7. - -and adds the following updates: - -- [Helm Chart: Updated MongoDB to 7.0.28 at artifacthub.io](https://github.com/wekan/charts/commit/5e6d344e0b976ce683116b66a1fb8417590115aa). - Thanks to xet7 and titver968. - -and fixes the following bugs: - -- [Re-add JS closing class to unicode close announcement symbol](https://github.com/wekan/wekan/pull/6050). - Thanks to Chostakovitch. -- [Cannot re-arrange lists within swimlanes](https://github.com/wekan/wekan/pull/6052). - Thanks to Chostakovitch. -- Converted Gantt from js to Jade, and made card title to render markdown at Gantt view. - [Part 1](https://github.com/wekan/wekan/commit/2d3bef9033134c3b62cf22179bbee4b6fea81444), - [Part 2](https://github.com/wekan/wekan/commit/3af3c9a89d8a4020b6f1ccada7da2ccbec1a8562). - Thanks to xet7. -- [Fix find.sh work with spaces, for example: ./find.sh "Some text"](https://github.com/wekan/wekan/commit/db4b04d8377523440fd2c36c1633ee74d7b05146). - Thanks to xet7. -- [Fix copy move card at board and MultiSelect to have numbered target of board, card above or below. Added MultiSelect change color](https://github.com/wekan/wekan/commit/74f1dfde72b9448645552ae28ba8d989d3e823d8). - Thanks to mimZD and xet7. -- [Fix move card last selection is gone](https://github.com/wekan/wekan/commit/2d87ba18b31ab5d8dc91dce01199cf7b313bd560). - Thanks to mimZD and xet7. -- [Fix Unable to delete Checklist. Added confirm delete to Checklist and Chekclist Item](https://github.com/wekan/wekan/commit/cf62807ad5d056ce9b8045c55f7cf6c29044967b). - Thanks to C0rn3j and xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.18 2025-12-28 WeKan ® release - -This release adds the following CRITICAL SECURITY FIXES: - -- [Upgraded MongoDB to 7.0.28 to fix MongoBleed at Snap Candidate](https://github.com/wekan/wekan/commit/e210c9973be55a4fa4e7dd15aefc24e06dbc3e7f). - Thanks to developers of MongoDB. - -and adds the following new features: - -- [Gantt chart view to one board view menu Swimlanes/Lists/Calendar/Gantt](https://github.com/wekan/wekan/commit/f34e4c0e363e386dbcce8e6ee8933b2d50491c58). - Thanks to xet7. -- [Number of cards per list and sum of custom number field in list head](https://github.com/wekan/wekan/commit/e569c2957ecc2b5fbf65ddcf0793b97c3ed5da81). - Thanks to xet7. -- [New Board Permissions: NormalAssignedOnly, CommentAssignedOnly, ReadOnly, ReadAssignedOnly](https://github.com/wekan/wekan/commit/c1168d181b3ff34f5ee7794a5740281c4ab5e253). - Thanks to xet7. -- [More translations. Added support page to Admin Panel / Settings / Layout](https://github.com/wekan/wekan/commit/a7400dca4503961267cc5fd6a1c8efaa78668f77). - Thanks to xet7. -- [Right top User Settings / Grey Icons. Also fixed Change Language popup](https://github.com/wekan/wekan/commit/300b653ea3416892faf2d08f5e0be3752e2041d6). - Thanks to xet7. -- [Collapse Swimlane, List, Opened Card. Opened Card window X and Y position can be moved freely from drag handle. Fix some dragging not possible. Fix iPhone Safari](https://github.com/wekan/wekan/commit/58f4884ad603e4f8c68a8819dfb1440234da70b6). - Thanks to xet7. -- Per-User and Board-level data save fixes. Per-User is collapse, width, height. Per-Board is Swimlanes, Lists, Cards etc. - [Part 1](https://github.com/wekan/wekan/commit/414b8dbf41ecf368d54aeceb6a78ccd0aa58f6a6), - [Part 2](https://github.com/wekan/wekan/commit/58e970d68508a76a1b9333941eb1696fb8fb7727). - Thanks to xet7. - -and adds the following updates: - -- [Update GitHub docker/metadata-action from 5.8.0 to 5.9.0](https://github.com/wekan/wekan/pull/6012). - Thanks to dependabot. -- [Updated security.md](https://github.com/wekan/wekan/commit/7ff1649d8909917cae590c68def6eecac0442f91). - Thanks to xet7. -- [Updated build script for Linux arm64 bundle](https://github.com/wekan/wekan/commit/3db1305e58168f7417023ccd8d54995026844b18). - Thanks to xet7. -- Update Backup docs about migrating to newest WeKan. - [Part 1](https://github.com/wekan/wekan/commit/e669b1b9c72278c8debbc9de74d3fa02224a66d8), - [Part 2](https://github.com/wekan/wekan/commit/19fa12bb26a0444acffd49f24123ed993c425f6a), - [Part 3](https://github.com/wekan/wekan/commit/4e346c0ab7fbfb39544063cbd0e095307b26648f), - [Part 4](https://github.com/wekan/wekan/commit/59fc756a0bda8e11b9d86961daa35bb755110a68), - [Part 5](https://github.com/wekan/wekan/commit/30541260f0f979662889bc40b4db461af1583a07), - [Part 6](https://github.com/wekan/wekan/commit/784c5c6b0c83397ab4344d1a0fa231f33ff26564), - [Part 7](https://github.com/wekan/wekan/commit/5686c92e05452a5d91c10ed436fae71103ecfb1f), - [Part 8](https://github.com/wekan/wekan/commit/b7ff370561153bbfbb07426f9bd8b4d2977b1d0c), - [Part 9](https://github.com/wekan/wekan/commit/fe4b36b85d4ac8efddb2c7148bc5d2413cd643e1), - [Part 10](https://github.com/wekan/wekan/commit/9ebdc82d46d86029df12adaafba95c0ecfc9d2c2), - [Part 11](https://github.com/wekan/wekan/commit/3ef0a3e685657eba1cc07314ac8d195f89dbef74), - [Part 12](https://github.com/wekan/wekan/commit/2cbf64da33aff2d0b77ee91e7e9ac360cd1edb99), - [Part 13](https://github.com/wekan/wekan/commit/3c578403404084ae10e4349b5570b0d50ecd8eb4), - [Part 14](https://github.com/wekan/wekan/commit/451e9f78705dbbac2ed6ce123fd5440a871b6dcc), - [Part 15](https://github.com/wekan/wekan/commit/e07e461e482f54c8ddaebc63373c93dc4aa0d956). - -and fixes the following bugs: - -- [Fix Broken Strikethroughs in Markdown to HTML conversion](https://github.com/wekan/wekan/pull/6009). - Thanks to brlin-tw. -- [Updated Mac docs for Applite](https://github.com/wekan/wekan/commit/400eb81206f346a973d871a8aaa55d4ac5d48753). - Thanks to xet7. -- [Fix checklist delete action (issue #6020), link-card popup defaults, and stabilize due-cards ordering](https://github.com/wekan/wekan/pull/5967). - Thanks to seve12. -- [Improve rules UI board dropdowns/loading, rule header titles, and ensure card move updates attachment metadata](https://github.com/wekan/wekan/pull/5967). - Thanks to seve12. -- [Improve imports: normalize id → _id, add default swimlane fallback, and add regression test](https://github.com/wekan/wekan/pull/5967). - Thanks to seve12. - -Thanks to above GitHub users for their contributions and translators for their translations. - -# v8.17 2025-11-06 WeKan ® release - -This release adds the following new feature: - -- [Feature: Workspaces, at All Boards page](https://github.com/wekan/wekan/commit/0afbdc95b49537e06b4f9cf98f51a669ef249384). - Thanks to xet7. - -and fixes the following bugs: - -- [Fix 8.16: Switching Board View fails with 403 error](https://github.com/wekan/wekan/commit/550d87ac6cb3ec946600616485afdbd242983ab4). - Thanks to xet7. -- [Moved migrations from opening board to right sidebar / Migrations](https://github.com/wekan/wekan/commit/1b25d1d5720d4f486a10d2acce37e315cf9b6057). - Thanks to xet7. -- [Fix 8.16 Lists with no items are deleted every time when board is opened. Moved migrations to right sidebar](https://github.com/wekan/wekan/commit/7713e613b431e44dc13cee72e7a1e5f031473fa6). - Thanks to xet7. -- [Remove old translations and code not in use anymore](https://github.com/wekan/wekan/commit/ba49d4d140bc0d4cfb5a96db9ab077bc85db58f1). - Thanks to xet7. -- [Fixed sidebar migrations to be per-board, not global. Clarified translations](https://github.com/wekan/wekan/commit/e4638d5fbcbe004ac393462331805cac3ba25097). - Thanks to xet7. -- [Fix star board](https://github.com/wekan/wekan/commit/8711b476be30496b96b845529b5717bb6e685c27). - Thanks to xet7. -- [Fix Card emoji issues](https://github.com/wekan/wekan/commit/e5e711c938edcca23c974c3eec97296898bcf24e). - Thanks to xet7. -- [Try to fix Edit Custom Fields button not working. Removed duplicate option from Boards Settings](https://github.com/wekan/wekan/commit/20af0a2ef55b11e7205845859ee92a929616ce91). - Thanks to xet7. -- [Fix Regression - calendar popup to set due date has gone](https://github.com/wekan/wekan/commit/581733d605b7e0494e72229c45947cff134f6dd6). - Thanks to xet7. -- [Remove not working Bookmark menu option](https://github.com/wekan/wekan/commit/c829c073cf822e48b7cd84bbfb79d42867412517). - Thanks to xet7. -- [Fix Workspaces at All Boards to have correct count of remaining etc, while starred also at Starred/Favorites](https://github.com/wekan/wekan/commit/6244657ca53a54646ec01e702851a51d89bd0d55). - Thanks to xet7. -- [Fix Worker Permissions does not allow for cards to be moved. - v8.15. Removed buttons Worker should not use](https://github.com/wekan/wekan/commit/18003900c2d497c129793d1653d4d9872a2f19da). - Thanks to xet7. - -Thanks to above GitHub users for their contributions and translators for their translations. - # v8.16 2025-11-02 WeKan ® release -This release fixes the following CRITICAL SECURITY ISSUES of [SpaceBleed](https://wekan.fi/hall-of-fame/spaceBleed/): +This release fixes SpaceBleed that is the following CRITICAL SECURITY ISSUES: - [Fix SECURITY ISSUE 1: File Attachments enables stored XSS (High)](https://github.com/wekan/wekan/commit/e9a727301d7b4f1689a703503df668c0f4f4cab8). Thanks to Siam Thanat Hack (STH) and xet7. @@ -3880,7 +3243,7 @@ Thanks to above GitHub users for their contributions and translators for their t This release fixes the following CRITICAL SECURITY ISSUES: -- Security Fix of FileBleed in WeKan. That is XSS in filename. +- Security Fix of Filebleed in WeKan. That is XSS in filename. [Part 1](https://github.com/wekan/wekan/commit/ff993e7c917b5650a790238e95c78001e4f0e039), [Part 2](https://github.com/wekan/wekan/commit/382168a5b428a7124d368c4fcb37e7e140e7ec8b). Thanks to responsible security disclosure contributors and xet7. diff --git a/Dockerfile b/Dockerfile index 8e697ccc3..2b90be928 100644 --- a/Dockerfile +++ b/Dockerfile @@ -4,23 +4,28 @@ LABEL org.opencontainers.image.ref.name="ubuntu" LABEL org.opencontainers.image.version="24.04" LABEL org.opencontainers.image.source="https://github.com/wekan/wekan" -# TARGETARCH is automatically provided by Docker Buildx -ARG TARGETARCH +# 2022-04-25: +# - gyp does not yet work with Ubuntu 22.04 ubuntu:rolling, +# so changing to 21.10. https://github.com/wekan/wekan/issues/4488 + +# 2021-09-18: +# - Above Ubuntu base image copied from Docker Hub ubuntu:hirsute-20210825 +# to Quay to avoid Docker Hub rate limits. ARG DEBIAN_FRONTEND=noninteractive -ENV BUILD_DEPS="apt-utils gnupg wget bzip2 g++ curl libarchive-tools build-essential git ca-certificates python3 unzip" +ENV BUILD_DEPS="apt-utils gnupg gosu wget bzip2 g++ curl libarchive-tools build-essential git ca-certificates python3 unzip" ENV \ DEBUG=false \ NODE_VERSION=v14.21.4 \ - METEOR_RELEASE=METEOR@2.16 \ + METEOR_RELEASE=METEOR@2.14 \ USE_EDGE=false \ METEOR_EDGE=1.5-beta.17 \ NPM_VERSION=6.14.17 \ FIBERS_VERSION=4.0.1 \ + ARCHITECTURE=linux-x64 \ SRC_PATH=./ \ WITH_API=true \ - MONGO_OPLOG_URL="" \ RESULTS_PER_PAGE="" \ DEFAULT_BOARD_ID="" \ ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 \ @@ -158,69 +163,134 @@ ENV \ MONGO_PASSWORD_FILE="" \ S3_SECRET_FILE="" +# NODE_OPTIONS="--max_old_space_size=4096" + +#--------------------------------------------- +# == at docker-compose.yml: AUTOLOGIN WITH OIDC/OAUTH2 ==== +# https://github.com/wekan/wekan/wiki/autologin +#- OIDC_REDIRECTION_ENABLED=true +#--------------------------------------------------------------------- + +# Copy the app to the image +#COPY ${SRC_PATH} /home/wekan/app + +# Install OS RUN < { - await processItem(item); // Runs all in parallel, not sequentially -}); - -// CORRECT: -for (const item of items) { - await processItem(item); // Runs sequentially -} -``` - ---- - -## 7. Client-Side Collection Updates - -Meteor requires client-side collection updates to use `_id` as the selector: - -```javascript -// CORRECT: -Lists.updateAsync(listId, { $set: { title: newTitle } }); - -// WRONG - fails with "Untrusted code may only update documents by ID": -Lists.updateAsync({ _id: listId, boardId: boardId }, { $set: { title: newTitle } }); -``` - ---- - -## 8. Sync Meteor 2.x APIs to Convert for 3.0 - -These Meteor 2.x sync APIs will need conversion when upgrading to Meteor 3.0: - -| Meteor 2.x (sync) | Meteor 3.0 (async) | -|--------------------|--------------------| -| `Collection.findOne()` | `Collection.findOneAsync()` | -| `Collection.find().fetch()` | `Collection.find().fetchAsync()` | -| `Collection.insert()` | `Collection.insertAsync()` | -| `Collection.update()` | `Collection.updateAsync()` | -| `Collection.remove()` | `Collection.removeAsync()` | -| `Collection.upsert()` | `Collection.upsertAsync()` | -| `Meteor.user()` | `Meteor.userAsync()` | -| `Meteor.userId()` | Remains sync | - -**Current status:** Server-side code already uses async patterns via `ReactiveCache`. The sync `findOne()` calls in allow/deny callbacks will need to be addressed when Meteor 3.0's allow/deny system supports async (or is replaced). - ---- - -## 9. Files Reference - -Key files involved in the async migration: - -| File | Role | -|------|------| -| `imports/reactiveCache.js` | ReactiveCache facade + Server/Client/Index implementations | -| `server/lib/utils.js` | Permission helper functions (`allowIsBoardMember*`) | -| `models/*.js` | Collection schemas, helpers, allow/deny, hooks, methods | -| `server/publications/*.js` | Meteor publications | -| `server/rulesHelper.js` | Rule trigger/action evaluation | -| `server/cronMigrationManager.js` | Cron-based migration jobs | - ---- - -## 10. FullCalendar Versioning Note (Post-3.0 Follow-Up) - -`wekan-fullcalendar` is currently migrated from legacy Meteor package globals to npm-based **FullCalendar 5.11.5** to keep Meteor 2.16 and 3.0 dual compatibility stable. - -**Why pinned for now:** -- Avoids introducing additional breaking changes during core Meteor async migration. -- Keeps compatibility with current Blaze/jQuery-era integration points while removing `momentjs:moment` Meteor package dependency. - -**After Meteor 3.0 lands (recommended follow-up):** -1. Re-evaluate upgrading FullCalendar to latest stable major. -2. Re-test plugin API differences (especially view names, callback signatures, locale/time formatting, CSS entry points). -3. Verify Node/runtime compatibility and bundle behavior under Meteor 3's final toolchain. -4. Keep migration isolated in a dedicated PR (separate from async data-layer work) to reduce rollback risk. diff --git a/SECURITY.md b/SECURITY.md index 346d7c1b0..0ad7a0256 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -1,33 +1,12 @@ +About money, see [CONTRIBUTING.md](CONTRIBUTING.md) -## Responsible Security Disclosure +Security is very important to us. If you discover any issue regarding security, please disclose +the information responsibly by sending an email from Protonmail to security@wekan.fi +that is Protomail email address, or by using this PGP key +[security-at-wekan.fi.asc](security-at-wekan.fi.asc) to security@wekan.fi +and not by creating a GitHub issue. We will respond swiftly to fix verifiable security issues. -1. To send email, if possible, use PGP key [security-at-wekan.fi.asc](security-at-wekan.fi.asc) -2. Send info about security issue ONLY to security@wekan.fi . NOT TO ANYWHERE ELSE. NO CC, NO BCC. -3. Wait for new WeKan release that fixes security issue to appear to top of - https://github.com/wekan/wekan/blob/main/CHANGELOG.md -4. We will thank you by adding you to Hall of Fame: https://wekan.fi/hall-of-fame/ -5. All vulnerability details will be private to security@wekan.fi , - unless you help all WeKan platforms to have a way to upgrade, like sending - database migrations code to security@wekan.fi or PRs to https://github.com/wekan/wekan/pulls . - There is no benefit to Wordwide Security Community to have more details about vulnerabilities, - if Worldwide Security Community does not help to make upgrades possible. -6. If there some day becomes available a way to upgrade all WeKan platforms, - this page will be updated to add permission for security researchers - to request new GHSA or CVE ID and publish your vulnerability details at your blog, talks, etc, - and send that info also to security@wekan.fi to be added to - Hall of Fame: https://wekan.fi/hall-of-fame/ to get Upgrade Bonus Point Stars. - In that case, it will become possible for security@wekan.fi to publish all - remaining private security details, and publicly thank Worldwide Security Community. - -## Bonus Points - -- If you include code for fixing security issue - -## Losing Points - -- If you ask about [bounty](CONTRIBUTING.md). There is no bounty. WeKan is NOT Big Tech. WeKan is FLOSS. -- If you forget to include vulnerability details. -- If you send info about security issue to somewhere else than security@wekan.fi +We thank you with a place at our hall of fame page, that is at https://wekan.fi/hall-of-fame ## How should reports be formatted? @@ -47,7 +26,7 @@ CWSS (optional): %cwss Anyone who reports a unique security issue in scope and does not disclose it to a third party before we have patched and updated may be upon their approval -added to the WeKan Hall of Fame https://wekan.fi/hall-of-fame/ +added to the Wekan Hall of Fame. ## Which domains are in scope? @@ -84,6 +63,11 @@ and by by companies that have 30k users. - If you are thinking about TLS MITM, look at https://github.com/caddyserver/caddy/issues/2530 - Let's Encrypt TLS requires publicly accessible webserver, that Let's Encrypt TLS validation servers check. - If firewall limits to only allowed IP addresses, you may need non-Let's Encrypt TLS cert. +- For On Premise: + - https://caddyserver.com/docs/automatic-https#local-https + - https://github.com/wekan/wekan/wiki/Caddy-Webserver-Config + - https://github.com/wekan/wekan/wiki/Azure + - https://github.com/wekan/wekan/wiki/Traefik-and-self-signed-SSL-certs ## XSS @@ -285,4 +269,9 @@ Typical already known or "no impact" bugs such as: - Email spoofing, SPF, DMARC & DKIM. Wekan does not include email server. Wekan is Open Source with MIT license, and free to use also for commercial use. -We welcome all fixes to improve security by email to security@wekan.fi +We welcome all fixes to improve security by email to security@wekan.team + +## Bonus Points + +If your Responsible Security Disclosure includes code for fixing security issue, +you get bonus points, as seen on [Hall of Fame](https://wekan.github.io/hall-of-fame). diff --git a/Stackerfile.yml b/Stackerfile.yml index e8d065119..544188a36 100644 --- a/Stackerfile.yml +++ b/Stackerfile.yml @@ -1,5 +1,5 @@ appId: wekan-public/apps/77b94f60-dec9-0136-304e-16ff53095928 -appVersion: "v8.35.0" +appVersion: "v8.16.0" files: userUploads: - README.md diff --git a/client/00-startup.js b/client/00-startup.js index f9aa8b4d2..52a1c536c 100644 --- a/client/00-startup.js +++ b/client/00-startup.js @@ -9,13 +9,12 @@ if ('serviceWorker' in navigator) { import '/client/lib/boardConverter'; import '/client/components/boardConversionProgress'; -// Import migration manager and progress UI - COMMENTED OUT -// import '/client/lib/attachmentMigrationManager'; -// import '/client/components/settings/migrationProgress'; +// Import migration manager and progress UI +import '/client/lib/migrationManager'; +import '/client/components/migrationProgress'; -// Import cron settings - COMMENTED OUT -// import '/client/components/settings/cronSettings'; -// Custom head tags +// Import cron settings +import '/client/components/settings/cronSettings'; // Mirror Meteor login token into a cookie for server-side file route auth // This enables cookie-based auth for /cdn/storage/* without leaking ROOT_URL @@ -63,21 +62,3 @@ Meteor.startup(() => { } }); }); - -// Subscribe to per-user small publications -Meteor.startup(() => { - Tracker.autorun(() => { - if (Meteor.userId()) { - Meteor.subscribe('userGreyIcons'); - Meteor.subscribe('userDesktopDragHandles'); - } - }); - - // Initialize mobile mode on startup for iOS devices - // This ensures mobile mode is applied correctly on page load - Tracker.afterFlush(() => { - if (typeof Utils !== 'undefined' && Utils.initializeUserSettings) { - Utils.initializeUserSettings(); - } - }); -}); diff --git a/client/components/activities/activities.jade b/client/components/activities/activities.jade index 346b4a4fb..f44673ae4 100644 --- a/client/components/activities/activities.jade +++ b/client/components/activities/activities.jade @@ -189,15 +189,14 @@ template(name="activity") if(currentData.timeKey) | {{_ activity.activityType }} = ' ' - i(title=currentData.timeValue).activity-meta {{ displayDate currentData.timeValue 'LLL' }} + i(title=currentData.timeValue).activity-meta {{ moment currentData.timeValue 'LLL' }} if (currentData.timeOldValue) = ' ' | {{{_ "previous_as" }}} = ' ' - i(title=currentData.timeOldValue).activity-meta {{ displayDate currentData.timeOldValue 'LLL' }} + i(title=currentData.timeOldValue).activity-meta {{ moment currentData.timeOldValue 'LLL' }} = ' @' else if(currentData.timeValue) | {{_ activity.activityType currentData.timeValue}} - if($neq mode 'none') - div(title=activity.createdAt).activity-meta {{ displayDate activity.createdAt }} + div(title=activity.createdAt).activity-meta {{ moment activity.createdAt }} diff --git a/client/components/activities/activities.js b/client/components/activities/activities.js index 436d56abe..5a0a81315 100644 --- a/client/components/activities/activities.js +++ b/client/components/activities/activities.js @@ -5,158 +5,164 @@ import { TAPi18n } from '/imports/i18n'; const activitiesPerPage = 500; -Template.activities.onCreated(function () { - // Register with sidebar so it can call loadNextPage on us - if (Sidebar) { - Sidebar.activitiesInstance = this; - } +BlazeComponent.extendComponent({ + onCreated() { + // XXX Should we use ReactiveNumber? + this.page = new ReactiveVar(1); + this.loadNextPageLocked = false; + // TODO is sidebar always available? E.g. on small screens/mobile devices + const sidebar = Sidebar; + sidebar && sidebar.callFirstWith(null, 'resetNextPeak'); + this.autorun(() => { + let mode = this.data()?.mode; + if (mode) { + const capitalizedMode = Utils.capitalize(mode); + let searchId; + const showActivities = this.showActivities(); + if (mode === 'linkedcard' || mode === 'linkedboard') { + const currentCard = Utils.getCurrentCard(); + searchId = currentCard.linkedId; + mode = mode.replace('linked', ''); + } else if (mode === 'card') { + searchId = Utils.getCurrentCardId(); + } else { + searchId = Session.get(`current${capitalizedMode}`); + } + const limit = this.page.get() * activitiesPerPage; + if (searchId === null) return; - // XXX Should we use ReactiveNumber? - this.page = new ReactiveVar(1); - this.loadNextPageLocked = false; - this.loadNextPage = () => { + this.subscribe('activities', mode, searchId, limit, showActivities, () => { + this.loadNextPageLocked = false; + + // TODO the guard can be removed as soon as the TODO above is resolved + if (!sidebar) return; + // If the sibear peak hasn't increased, that mean that there are no more + // activities, and we can stop calling new subscriptions. + // XXX This is hacky! We need to know excatly and reactively how many + // activities there are, we probably want to denormalize this number + // dirrectly into card and board documents. + const nextPeakBefore = sidebar.callFirstWith(null, 'getNextPeak'); + sidebar.calculateNextPeak(); + const nextPeakAfter = sidebar.callFirstWith(null, 'getNextPeak'); + if (nextPeakBefore === nextPeakAfter) { + sidebar.callFirstWith(null, 'resetNextPeak'); + } + }); + } + }); + }, + loadNextPage() { if (this.loadNextPageLocked === false) { this.page.set(this.page.get() + 1); this.loadNextPageLocked = true; } - }; - - // TODO is sidebar always available? E.g. on small screens/mobile devices - const sidebar = Sidebar; - if (sidebar && sidebar.infiniteScrolling) { - sidebar.infiniteScrolling.resetNextPeak(); - } - this.autorun(() => { - const data = Template.currentData(); - let mode = data?.mode; + }, + showActivities() { + let ret = false; + let mode = this.data()?.mode; if (mode) { - const capitalizedMode = Utils.capitalize(mode); - let searchId; - const showActivities = _showActivities(data); if (mode === 'linkedcard' || mode === 'linkedboard') { const currentCard = Utils.getCurrentCard(); - searchId = currentCard.linkedId; - mode = mode.replace('linked', ''); + ret = currentCard.showActivities ?? false; } else if (mode === 'card') { - searchId = Utils.getCurrentCardId(); + ret = this.data()?.card?.showActivities ?? false; } else { - searchId = Session.get(`current${capitalizedMode}`); + ret = Utils.getCurrentBoard().showActivities ?? false; } - const limit = this.page.get() * activitiesPerPage; - if (searchId === null) return; - - this.subscribe('activities', mode, searchId, limit, showActivities, () => { - this.loadNextPageLocked = false; - - // TODO the guard can be removed as soon as the TODO above is resolved - if (!sidebar || !sidebar.infiniteScrolling) return; - // If the sidebar peak hasn't increased, that means that there are no more - // activities, and we can stop calling new subscriptions. - const nextPeakBefore = sidebar.infiniteScrolling.getNextPeak(); - sidebar.calculateNextPeak(); - const nextPeakAfter = sidebar.infiniteScrolling.getNextPeak(); - if (nextPeakBefore === nextPeakAfter) { - sidebar.infiniteScrolling.resetNextPeak(); - } - }); } - }); -}); - -function _showActivities(data) { - let ret = false; - let mode = data?.mode; - if (mode) { - if (mode === 'linkedcard' || mode === 'linkedboard') { - const currentCard = Utils.getCurrentCard(); - ret = currentCard.showActivities ?? false; - } else if (mode === 'card') { - ret = data?.card?.showActivities ?? false; - } else { - ret = Utils.getCurrentBoard().showActivities ?? false; - } - } - return ret; -} - -Template.activities.helpers({ - activities() { - return this.card.activities(); + return ret; }, -}); + activities() { + const ret = this.data().card.activities(); + return ret; + }, +}).register('activities'); -Template.activity.helpers({ +BlazeComponent.extendComponent({ checkItem() { - const checkItemId = this.activity.checklistItemId; + const checkItemId = this.currentData().activity.checklistItemId; const checkItem = ReactiveCache.getChecklistItem(checkItemId); return checkItem && checkItem.title; }, boardLabelLink() { + const data = this.currentData(); const currentBoardId = Session.get('currentBoard'); - if (this.mode !== 'board') { - return createBoardLink(this.activity.board(), this.activity.listName ? this.activity.listName : null); + if (data.mode !== 'board') { + // data.mode: card, linkedcard, linkedboard + return createBoardLink(data.activity.board(), data.activity.listName ? data.activity.listName : null); } - else if (currentBoardId != this.activity.boardId) { - return createBoardLink(this.activity.board(), this.activity.listName ? this.activity.listName : null); + else if (currentBoardId != data.activity.boardId) { + // data.mode: board + // current activitie is linked + return createBoardLink(data.activity.board(), data.activity.listName ? data.activity.listName : null); } return TAPi18n.__('this-board'); }, cardLabelLink() { + const data = this.currentData(); const currentBoardId = Session.get('currentBoard'); - if (this.mode == 'card') { + if (data.mode == 'card') { + // data.mode: card return TAPi18n.__('this-card'); } - else if (this.mode !== 'board') { - return createCardLink(this.activity.card(), null); + else if (data.mode !== 'board') { + // data.mode: linkedcard, linkedboard + return createCardLink(data.activity.card(), null); } - else if (currentBoardId != this.activity.boardId) { - return createCardLink(this.activity.card(), this.activity.board().title); + else if (currentBoardId != data.activity.boardId) { + // data.mode: board + // current activitie is linked + return createCardLink(data.activity.card(), data.activity.board().title); } - return createCardLink(this.activity.card(), null); + return createCardLink(this.currentData().activity.card(), null); }, cardLink() { + const data = this.currentData(); const currentBoardId = Session.get('currentBoard'); - if (this.mode !== 'board') { - return createCardLink(this.activity.card(), null); + if (data.mode !== 'board') { + // data.mode: card, linkedcard, linkedboard + return createCardLink(data.activity.card(), null); } - else if (currentBoardId != this.activity.boardId) { - return createCardLink(this.activity.card(), this.activity.board().title); + else if (currentBoardId != data.activity.boardId) { + // data.mode: board + // current activitie is linked + return createCardLink(data.activity.card(), data.activity.board().title); } - return createCardLink(this.activity.card(), null); + return createCardLink(this.currentData().activity.card(), null); }, receivedDate() { - const card = this.activity.card(); - if (!card) return null; - return card.receivedAt; + const receivedDate = this.currentData().activity.card(); + if (!receivedDate) return null; + return receivedDate.receivedAt; }, startDate() { - const card = this.activity.card(); - if (!card) return null; - return card.startAt; + const startDate = this.currentData().activity.card(); + if (!startDate) return null; + return startDate.startAt; }, dueDate() { - const card = this.activity.card(); - if (!card) return null; - return card.dueAt; + const dueDate = this.currentData().activity.card(); + if (!dueDate) return null; + return dueDate.dueAt; }, endDate() { - const card = this.activity.card(); - if (!card) return null; - return card.endAt; + const endDate = this.currentData().activity.card(); + if (!endDate) return null; + return endDate.endAt; }, lastLabel() { - const lastLabelId = this.activity.labelId; + const lastLabelId = this.currentData().activity.labelId; if (!lastLabelId) return null; const lastLabel = ReactiveCache.getBoard( - this.activity.boardId, + this.currentData().activity.boardId, ).getLabelById(lastLabelId); if (lastLabel && (lastLabel.name === undefined || lastLabel.name === '')) { return lastLabel.color; @@ -169,7 +175,7 @@ Template.activity.helpers({ lastCustomField() { const lastCustomField = ReactiveCache.getCustomField( - this.activity.customFieldId, + this.currentData().activity.customFieldId, ); if (!lastCustomField) return null; return lastCustomField.name; @@ -177,10 +183,10 @@ Template.activity.helpers({ lastCustomFieldValue() { const lastCustomField = ReactiveCache.getCustomField( - this.activity.customFieldId, + this.currentData().activity.customFieldId, ); if (!lastCustomField) return null; - const value = this.activity.value; + const value = this.currentData().activity.value; if ( lastCustomField.settings.dropdownItems && lastCustomField.settings.dropdownItems.length > 0 @@ -197,13 +203,13 @@ Template.activity.helpers({ }, listLabel() { - const activity = this.activity; + const activity = this.currentData().activity; const list = activity.list(); return (list && list.title) || activity.title; }, sourceLink() { - const source = this.activity.source; + const source = this.currentData().activity.source; if (source) { if (source.url) { return Blaze.toHTML( @@ -223,12 +229,12 @@ Template.activity.helpers({ memberLink() { return Blaze.toHTMLWithData(Template.memberName, { - user: this.activity.member(), + user: this.currentData().activity.member(), }); }, attachmentLink() { - const attachment = this.activity.attachment(); + const attachment = this.currentData().activity.attachment(); // trying to display url before file is stored generates js errors return ( (attachment && @@ -242,16 +248,17 @@ Template.activity.helpers({ sanitizeText(attachment.name), ), )) || - sanitizeText(this.activity.attachmentName) + sanitizeText(this.currentData().activity.attachmentName) ); }, customField() { - const customField = this.activity.customField(); + const customField = this.currentData().activity.customField(); if (!customField) return null; return customField.name; }, -}); + +}).register('activity'); Template.activity.helpers({ sanitize(value) { diff --git a/client/components/activities/comments.css b/client/components/activities/comments.css index de0189de7..f495ca361 100644 --- a/client/components/activities/comments.css +++ b/client/components/activities/comments.css @@ -108,12 +108,15 @@ text-decoration: none; height: 24px; } -.comments .comment .comment-desc .reactions .open-comment-reaction-popup span { - display: inline-block; - font-size: clamp(14px, 2vw, 18px); +.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-smile-o { + font-size: 17px; font-weight: 500; - line-height: 1; - margin-left: 4px; + margin-left: 2px; +} +.comments .comment .comment-desc .reactions .open-comment-reaction-popup i.fa.fa-plus { + font-size: 8px; + margin-top: -7px; + margin-left: 1px; } .comments .comment .comment-desc .reactions .reaction { cursor: pointer; diff --git a/client/components/activities/comments.jade b/client/components/activities/comments.jade index cbf497a67..07b52d47d 100644 --- a/client/components/activities/comments.jade +++ b/client/components/activities/comments.jade @@ -25,14 +25,13 @@ template(name="comment") = text .edit-controls button.primary(type="submit") {{_ 'edit'}} - a.js-close-inlined-form(title="{{_ 'close' }}") - i.fa.fa-times-thin + .fa.fa-times-thin.js-close-inlined-form else .comment-text +viewer = text +commentReactions(reactions=reactions commentId=_id) - span(title=createdAt).comment-meta {{ displayDate createdAt }} + span(title=createdAt).comment-meta {{ moment createdAt }} if($eq currentUser._id userId) +editOrDeleteComment else if currentUser.isBoardAdmin @@ -55,11 +54,9 @@ template(name="commentReactions") span.reaction-codepoint !{reaction.reactionCodepoint} span.reaction-count #{reaction.userIds.length} if (currentUser.isBoardMember) - unless currentUser.isReadOnly - unless currentUser.isReadAssignedOnly - a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}") - span(title="{{_ 'reaction' }}") 😀 - span(title="{{_ 'add' }}") ➕ + a.open-comment-reaction-popup(title="{{_ 'addReactionPopup-title'}}") + i.fa.fa-smile-o + i.fa.fa-plus template(name="addReactionPopup") .reactions-popup diff --git a/client/components/activities/comments.js b/client/components/activities/comments.js index bc8444589..62629252d 100644 --- a/client/components/activities/comments.js +++ b/client/components/activities/comments.js @@ -2,79 +2,93 @@ import { ReactiveCache } from '/imports/reactiveCache'; const commentFormIsOpen = new ReactiveVar(false); -Template.commentForm.onDestroyed(function () { - commentFormIsOpen.set(false); - $('.note-popover').hide(); -}); +BlazeComponent.extendComponent({ + onDestroyed() { + commentFormIsOpen.set(false); + $('.note-popover').hide(); + }, -Template.commentForm.helpers({ commentFormIsOpen() { return commentFormIsOpen.get(); }, -}); -Template.commentForm.events({ - 'submit .js-new-comment-form'(evt, tpl) { - const input = tpl.$('.js-new-comment-input'); - const text = input.val().trim(); - const card = Template.currentData(); - let boardId = card.boardId; - let cardId = card._id; - if (card.isLinkedCard()) { - boardId = ReactiveCache.getCard(card.linkedId).boardId; - cardId = card.linkedId; - } else if (card.isLinkedBoard()) { - boardId = card.linkedId; - } - if (text) { - CardComments.insert({ - text, - boardId, - cardId, - }); - resetCommentInput(input); - Tracker.flush(); - autosize.update(input); - input.trigger('submitted'); - } - evt.preventDefault(); + getInput() { + return this.$('.js-new-comment-input'); }, - // Pressing Ctrl+Enter should submit the form - 'keydown form textarea'(evt, tpl) { - if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) { - tpl.find('button[type=submit]').click(); - } - }, -}); -Template.comments.helpers({ - getComments() { - const data = Template.currentData(); - if (!data || typeof data.comments !== 'function') return []; - return data.comments(); - }, -}); - -Template.comment.events({ - 'click .js-delete-comment': Popup.afterConfirm('deleteComment', function () { - const commentId = this._id; - CardComments.remove(commentId); - Popup.back(); - }), - 'submit .js-edit-comment'(evt, tpl) { - evt.preventDefault(); - const textarea = tpl.find('.js-edit-comment textarea,input[type=text]'); - const commentText = textarea && textarea.value ? textarea.value.trim() : ''; - const commentId = this._id; - if (commentText) { - CardComments.update(commentId, { - $set: { - text: commentText, + events() { + return [ + { + 'submit .js-new-comment-form'(evt) { + const input = this.getInput(); + const text = input.val().trim(); + const card = this.currentData(); + let boardId = card.boardId; + let cardId = card._id; + if (card.isLinkedCard()) { + boardId = ReactiveCache.getCard(card.linkedId).boardId; + cardId = card.linkedId; + } else if (card.isLinkedBoard()) { + boardId = card.linkedId; + } + if (text) { + CardComments.insert({ + text, + boardId, + cardId, + }); + resetCommentInput(input); + Tracker.flush(); + autosize.update(input); + input.trigger('submitted'); + } + evt.preventDefault(); }, - }); - } + // Pressing Ctrl+Enter should submit the form + 'keydown form textarea'(evt) { + if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) { + this.find('button[type=submit]').click(); + } + }, + }, + ]; }, -}); +}).register('commentForm'); + +BlazeComponent.extendComponent({ + getComments() { + const ret = this.data().comments(); + return ret; + }, +}).register("comments"); + +BlazeComponent.extendComponent({ + events() { + return [ + { + 'click .js-delete-comment': Popup.afterConfirm('deleteComment', () => { + const commentId = this.data()._id; + CardComments.remove(commentId); + Popup.back(); + }), + 'submit .js-edit-comment'(evt) { + evt.preventDefault(); + const commentText = this.currentComponent() + .getValue() + .trim(); + const commentId = this.data()._id; + if (commentText) { + CardComments.update(commentId, { + $set: { + text: commentText, + }, + }); + } + }, + }, + ]; + }, +}).register("comment"); // XXX This should be a static method of the `commentForm` component function resetCommentInput(input) { diff --git a/client/components/boardConversionProgress.css b/client/components/boardConversionProgress.css index 7c17e561e..fd186908f 100644 --- a/client/components/boardConversionProgress.css +++ b/client/components/boardConversionProgress.css @@ -170,14 +170,14 @@ width: 95%; margin: 20px; } - + .board-conversion-header, .board-conversion-content, .board-conversion-footer { padding-left: 16px; padding-right: 16px; } - + .board-conversion-header h3 { font-size: 18px; } diff --git a/client/components/boardConversionProgress.jade b/client/components/boardConversionProgress.jade index 77b0321c0..37a404d90 100644 --- a/client/components/boardConversionProgress.jade +++ b/client/components/boardConversionProgress.jade @@ -3,25 +3,25 @@ template(name="boardConversionProgress") .board-conversion-modal .board-conversion-header h3 - i.fa.fa-cog + | ⚙️ | {{_ 'converting-board'}} p {{_ 'converting-board-description'}} - + .board-conversion-content .conversion-progress .progress-bar .progress-fill(style="width: {{conversionProgress}}%") .progress-text {{conversionProgress}}% - + .conversion-status - i.fa.fa-cog + | ⚙️ | {{conversionStatus}} - + .conversion-time(style="{{#unless conversionEstimatedTime}}display: none;{{/unless}}") - i.fa.fa-clock-o + | ⏰ | {{_ 'estimated-time-remaining'}}: {{conversionEstimatedTime}} - + .board-conversion-footer .conversion-info - i.fa.fa-info-circle + | ℹ️ | {{_ 'conversion-info-text'}} diff --git a/client/components/boardConversionProgress.js b/client/components/boardConversionProgress.js index 4dd7bfb41..454df5006 100644 --- a/client/components/boardConversionProgress.js +++ b/client/components/boardConversionProgress.js @@ -1,6 +1,6 @@ import { Template } from 'meteor/templating'; import { ReactiveVar } from 'meteor/reactive-var'; -import { +import { boardConverter, isConverting, conversionProgress, @@ -12,15 +12,15 @@ Template.boardConversionProgress.helpers({ isConverting() { return isConverting.get(); }, - + conversionProgress() { return conversionProgress.get(); }, - + conversionStatus() { return conversionStatus.get(); }, - + conversionEstimatedTime() { return conversionEstimatedTime.get(); } diff --git a/client/components/boards/boardArchive.jade b/client/components/boards/boardArchive.jade index 76f3be0a5..d9a251ebd 100644 --- a/client/components/boards/boardArchive.jade +++ b/client/components/boards/boardArchive.jade @@ -1,7 +1,6 @@ template(name="archivedBoards") h2 - span(title="{{_ 'archived-boards'}}") - i.fa.fa-archive + i.fa.fa-archive | {{_ 'archived-boards'}} ul.archived-lists @@ -9,13 +8,13 @@ template(name="archivedBoards") li.archived-lists-item div.board-header-btns button.board-header-btn.js-delete-board - i.fa.fa-trash + i.fa.fa-trash-o | {{_ 'delete-board'}} button.board-header-btn.js-restore-board i.fa.fa-undo | {{_ 'restore-board'}} = title - span {{ displayDate archivedAt 'LLL' }} + span {{ moment archivedAt 'LLL' }} else li.no-items-message {{_ 'no-archived-boards'}} diff --git a/client/components/boards/boardArchive.js b/client/components/boards/boardArchive.js index eee55d0be..87525c1f7 100644 --- a/client/components/boards/boardArchive.js +++ b/client/components/boards/boardArchive.js @@ -1,11 +1,10 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; -Template.archivedBoards.onCreated(function () { - this.subscribe('archivedBoards'); -}); +BlazeComponent.extendComponent({ + onCreated() { + this.subscribe('archivedBoards'); + }, -Template.archivedBoards.helpers({ isBoardAdmin() { return ReactiveCache.getCurrentUser().isBoardAdmin(); }, @@ -19,34 +18,38 @@ Template.archivedBoards.helpers({ ); return ret; }, -}); -Template.archivedBoards.events({ - async 'click .js-restore-board'() { - // TODO : Make isSandstorm variable global - const isSandstorm = - Meteor.settings && - Meteor.settings.public && - Meteor.settings.public.sandstorm; - if (isSandstorm && Utils.getCurrentBoardId()) { - const currentBoard = Utils.getCurrentBoard(); - await currentBoard.archive(); - } - const board = this; - await board.restore(); - Utils.goBoardId(board._id); + events() { + return [ + { + 'click .js-restore-board'() { + // TODO : Make isSandstorm variable global + const isSandstorm = + Meteor.settings && + Meteor.settings.public && + Meteor.settings.public.sandstorm; + if (isSandstorm && Utils.getCurrentBoardId()) { + const currentBoard = Utils.getCurrentBoard(); + currentBoard.archive(); + } + const board = this.currentData(); + board.restore(); + Utils.goBoardId(board._id); + }, + 'click .js-delete-board': Popup.afterConfirm('boardDelete', function() { + Popup.back(); + const isSandstorm = + Meteor.settings && + Meteor.settings.public && + Meteor.settings.public.sandstorm; + if (isSandstorm && Utils.getCurrentBoardId()) { + const currentBoard = Utils.getCurrentBoard(); + Boards.remove(currentBoard._id); + } + Boards.remove(this._id); + FlowRouter.go('home'); + }), + }, + ]; }, - 'click .js-delete-board': Popup.afterConfirm('boardDelete', async function() { - Popup.back(); - const isSandstorm = - Meteor.settings && - Meteor.settings.public && - Meteor.settings.public.sandstorm; - if (isSandstorm && Utils.getCurrentBoardId()) { - const currentBoard = Utils.getCurrentBoard(); - await Boards.removeAsync(currentBoard._id); - } - await Boards.removeAsync(this._id); - FlowRouter.go('home'); - }), -}); +}).register('archivedBoards'); diff --git a/client/components/boards/boardBody.css b/client/components/boards/boardBody.css index d8b13ba8c..f65cbaffc 100644 --- a/client/components/boards/boardBody.css +++ b/client/components/boards/boardBody.css @@ -231,30 +231,6 @@ font-size: 1em !important; /* Keep original icon size */ } -/* Mobile iPhone: scale card details text and icons to 2x */ -body.mobile-mode.iphone-device .card-details { - font-size: 2em !important; -} -body.mobile-mode.iphone-device .card-details .fa, -body.mobile-mode.iphone-device .card-details .icon, -body.mobile-mode.iphone-device .card-details i, -body.mobile-mode.iphone-device .card-details .emoji-icon, -body.mobile-mode.iphone-device .card-details a, -body.mobile-mode.iphone-device .card-details p, -body.mobile-mode.iphone-device .card-details span, -body.mobile-mode.iphone-device .card-details div, -body.mobile-mode.iphone-device .card-details button, -body.mobile-mode.iphone-device .card-details input, -body.mobile-mode.iphone-device .card-details select, -body.mobile-mode.iphone-device .card-details textarea { - font-size: inherit !important; -} -/* Section titles slightly larger than content but not as big as card title */ -body.mobile-mode.iphone-device .card-details .card-details-item-title { - font-size: 1.1em !important; - font-weight: bold; -} - /* Ensure scrollbars are positioned correctly */ #content[style*="overflow-x: auto"]::-webkit-scrollbar:vertical { width: 12px; @@ -287,35 +263,6 @@ body.mobile-mode.iphone-device .card-details .card-details-item-title { animation: fadeIn 0.2s; z-index: 16; } - -/* Fix for mobile Safari: ensure overlay stays behind card details */ -@media screen and (max-width: 800px) { - .board-wrapper .board-canvas .board-overlay { - z-index: 17 !important; - } - - /* In desktop mode on small screens, still keep overlay behind card */ - body.desktop-mode .board-wrapper .board-canvas .board-overlay { - z-index: 17 !important; - } -} - -/* In mobile mode, lower the overlay z-index to stay behind card details */ -body.mobile-mode .board-wrapper .board-canvas .board-overlay { - z-index: 17 !important; -} - -/* iPhone in desktop mode: remove overlay to avoid blocking card */ -body.desktop-mode.iphone-device .board-wrapper .board-canvas .board-overlay { - display: none !important; - pointer-events: none !important; -} - -/* Desktop mode: hide overlay to allow multiple cards and board interaction */ -body.desktop-mode .board-wrapper .board-canvas .board-overlay { - display: none !important; - pointer-events: none !important; -} .board-wrapper .board-canvas.is-dragging-active .open-minicard-composer, .board-wrapper .board-canvas.is-dragging-active .minicard-wrapper.is-checked { display: none; diff --git a/client/components/boards/boardBody.jade b/client/components/boards/boardBody.jade index 2a4e902ce..d2118112b 100644 --- a/client/components/boards/boardBody.jade +++ b/client/components/boards/boardBody.jade @@ -1,8 +1,10 @@ template(name="board") - - if isConverting + + if isMigrating.get + +migrationProgress + else if isConverting.get +boardConversionProgress - else if isBoardReady + else if isBoardReady.get if currentBoard if onlyShowCurrentCard +cardDetails(currentCard) @@ -22,16 +24,16 @@ template(name="boardBody") // Debug information (remove in production) if debugBoardState .debug-info(style="position: fixed; top: 0; left: 0; background: rgba(0,0,0,0.8); color: white; padding: 10px; z-index: 9999; font-size: 12px;") - | {{_ 'board'}}: {{currentBoard.title}} | {{_ 'view'}}: {{boardView}} | {{_ 'has-swimlanes'}}: {{hasSwimlanes}} | {{_ 'swimlanes'}}: {{currentBoard.swimlanes.length}} + | Board: {{currentBoard.title}} | View: {{boardView}} | HasSwimlanes: {{hasSwimlanes}} | Swimlanes: {{currentBoard.swimlanes.length}} .board-wrapper(class=currentBoard.colorClass class="{{#if isMiniScreen}}mobile-view{{/if}}") .board-canvas.js-swimlanes( class="{{#if hasSwimlanes}}dragscroll{{/if}}" class="{{#if Sidebar.isOpen}}is-sibling-sidebar-open{{/if}}" class="{{#if MultiSelection.isActive}}is-multiselection-active{{/if}}" - class="{{#if draggingActive}}is-dragging-active{{/if}}" + class="{{#if draggingActive.get}}is-dragging-active{{/if}}" class="{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}" class="{{#if isMiniScreen}}mobile-view{{/if}}") - if showOverlay + if showOverlay.get .board-overlay if currentBoard.isTemplatesBoard each currentBoard.swimlanes @@ -47,8 +49,6 @@ template(name="boardBody") +listsGroup(currentBoard) else if isViewCalendar +calendarView - else if isViewGantt - +ganttView else // Default view - show swimlanes if they exist, otherwise show lists if hasSwimlanes @@ -56,10 +56,6 @@ template(name="boardBody") +swimlane(this) else +listsGroup(currentBoard) - //- Render multiple open cards in desktop mode - unless isMiniScreen - each openCards - +cardDetails(this cardIndex=@index) +sidebar template(name="calendarView") @@ -67,4 +63,4 @@ template(name="calendarView") .calendar-view.swimlane if currentCard +cardDetails(currentCard) - +fullcalendar(calendarOptions) \ No newline at end of file + +fullcalendar(calendarOptions) diff --git a/client/components/boards/boardBody.js b/client/components/boards/boardBody.js index 56d64dbe2..e8e83a134 100644 --- a/client/components/boards/boardBody.js +++ b/client/components/boards/boardBody.js @@ -1,10 +1,10 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import '../gantt/gantt.js'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import { TAPi18n } from '/imports/i18n'; import dragscroll from '@wekanteam/dragscroll'; import { boardConverter } from '/client/lib/boardConverter'; -import { formatDateByUserPreference } from '/imports/lib/dateUtils'; +import { migrationManager } from '/client/lib/migrationManager'; +import { attachmentMigrationManager } from '/client/lib/attachmentMigrationManager'; +import { migrationProgressManager } from '/client/components/migrationProgress'; import Swimlanes from '/models/swimlanes'; import Lists from '/models/lists'; @@ -12,17 +12,53 @@ const subManager = new SubsManager(); const { calculateIndex } = Utils; const swimlaneWhileSortingHeight = 150; -// Global reference so child components (swimlanes, cards) can access boardBody instance -BoardBody = null; +BlazeComponent.extendComponent({ + onCreated() { + this.isBoardReady = new ReactiveVar(false); + this.isConverting = new ReactiveVar(false); + this.isMigrating = new ReactiveVar(false); + this._swimlaneCreated = new Set(); // Track boards where we've created swimlanes + this._boardProcessed = false; // Track if board has been processed + this._lastProcessedBoardId = null; // Track last processed board ID -Template.board.onCreated(function () { - this.isBoardReady = new ReactiveVar(false); - this.isConverting = new ReactiveVar(false); - this._swimlaneCreated = new Set(); // Track boards where we've created swimlanes - this._boardProcessed = false; // Track if board has been processed - this._lastProcessedBoardId = null; // Track last processed board ID + // The pattern we use to manually handle data loading is described here: + // https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management/using-subs-manager + // XXX The boardId should be readed from some sort the component "props", + // unfortunatly, Blaze doesn't have this notion. + this.autorun(() => { + const currentBoardId = Session.get('currentBoard'); + if (!currentBoardId) return; + + const handle = subManager.subscribe('board', currentBoardId, false); + + // Use a separate autorun for subscription ready state to avoid reactive loops + this.subscriptionReadyAutorun = Tracker.autorun(() => { + if (handle.ready()) { + // Only run conversion/migration logic once per board + if (!this._boardProcessed || this._lastProcessedBoardId !== currentBoardId) { + this._boardProcessed = true; + this._lastProcessedBoardId = currentBoardId; + + // Ensure default swimlane exists (only once per board) + this.ensureDefaultSwimlane(currentBoardId); + // Check if board needs conversion + this.checkAndConvertBoard(currentBoardId); + } + } else { + this.isBoardReady.set(false); + } + }); + }); + }, - this.ensureDefaultSwimlane = (boardId) => { + onDestroyed() { + // Clean up the subscription ready autorun to prevent memory leaks + if (this.subscriptionReadyAutorun) { + this.subscriptionReadyAutorun.stop(); + } + }, + + ensureDefaultSwimlane(boardId) { // Only create swimlane once per board if (this._swimlaneCreated.has(boardId)) { return; @@ -33,7 +69,7 @@ Template.board.onCreated(function () { if (!board) return; const swimlanes = board.swimlanes(); - + if (swimlanes.length === 0) { // Check if any swimlane exists in the database to avoid race conditions const existingSwimlanes = ReactiveCache.getSwimlanes({ boardId }); @@ -43,9 +79,7 @@ Template.board.onCreated(function () { boardId: boardId, }); if (process.env.DEBUG === 'true') { - console.log( - `Created default swimlane ${swimlaneId} for board ${boardId}`, - ); + console.log(`Created default swimlane ${swimlaneId} for board ${boardId}`); } } this._swimlaneCreated.add(boardId); @@ -55,9 +89,9 @@ Template.board.onCreated(function () { } catch (error) { console.error('Error creating default swimlane:', error); } - }; + }, - this.checkAndConvertBoard = async (boardId) => { + async checkAndConvertBoard(boardId) { try { const board = ReactiveCache.getBoard(boardId); if (!board) { @@ -65,631 +99,901 @@ Template.board.onCreated(function () { return; } - this.isBoardReady.set(true); + // Check if board needs comprehensive migration + const needsMigration = await this.checkComprehensiveMigration(boardId); + + if (needsMigration) { + // Start comprehensive migration + this.isMigrating.set(true); + const success = await this.executeComprehensiveMigration(boardId); + this.isMigrating.set(false); + + if (success) { + this.isBoardReady.set(true); + } else { + console.error('Comprehensive migration failed, setting ready to true anyway'); + this.isBoardReady.set(true); // Still show board even if migration failed + } + } else { + this.isBoardReady.set(true); + } + } catch (error) { console.error('Error during board conversion check:', error); this.isConverting.set(false); + this.isMigrating.set(false); this.isBoardReady.set(true); // Show board even if conversion check failed } - }; + }, - // The pattern we use to manually handle data loading is described here: - // https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management/using-subs-manager - // XXX The boardId should be readed from some sort the component "props", - // unfortunatly, Blaze doesn't have this notion. - this.autorun(() => { - const currentBoardId = Session.get('currentBoard'); - if (!currentBoardId) return; + /** + * Check if board needs comprehensive migration + */ + async checkComprehensiveMigration(boardId) { + try { + return new Promise((resolve, reject) => { + Meteor.call('comprehensiveBoardMigration.needsMigration', boardId, (error, result) => { + if (error) { + console.error('Error checking comprehensive migration:', error); + reject(error); + } else { + resolve(result); + } + }); + }); + } catch (error) { + console.error('Error checking comprehensive migration:', error); + return false; + } + }, - const handle = subManager.subscribe('board', currentBoardId, false); + /** + * Execute comprehensive migration for a board + */ + async executeComprehensiveMigration(boardId) { + try { + // Start progress tracking + migrationProgressManager.startMigration(); + + // Simulate progress updates since we can't easily pass callbacks through Meteor methods + const progressSteps = [ + { step: 'analyze_board_structure', name: 'Analyze Board Structure', duration: 1000 }, + { step: 'fix_orphaned_cards', name: 'Fix Orphaned Cards', duration: 2000 }, + { step: 'convert_shared_lists', name: 'Convert Shared Lists', duration: 3000 }, + { step: 'ensure_per_swimlane_lists', name: 'Ensure Per-Swimlane Lists', duration: 1500 }, + { step: 'cleanup_empty_lists', name: 'Cleanup Empty Lists', duration: 1000 }, + { step: 'validate_migration', name: 'Validate Migration', duration: 1000 }, + { step: 'fix_avatar_urls', name: 'Fix Avatar URLs', duration: 1000 }, + { step: 'fix_attachment_urls', name: 'Fix Attachment URLs', duration: 1000 } + ]; - // Use a separate autorun for subscription ready state to avoid reactive loops - this.subscriptionReadyAutorun = Tracker.autorun(() => { - if (handle.ready()) { - if ( - !this._boardProcessed || - this._lastProcessedBoardId !== currentBoardId - ) { - this._boardProcessed = true; - this._lastProcessedBoardId = currentBoardId; + // Start the actual migration + const migrationPromise = new Promise((resolve, reject) => { + Meteor.call('comprehensiveBoardMigration.execute', boardId, (error, result) => { + if (error) { + console.error('Error executing comprehensive migration:', error); + migrationProgressManager.failMigration(error); + reject(error); + } else { + if (process.env.DEBUG === 'true') { + console.log('Comprehensive migration completed for board:', boardId, result); + } + resolve(result.success); + } + }); + }); - // Ensure default swimlane exists (only once per board) - this.ensureDefaultSwimlane(currentBoardId); - // Check if board needs conversion - this.checkAndConvertBoard(currentBoardId); - } - } else { - this.isBoardReady.set(false); + // Simulate progress updates + const progressPromise = this.simulateMigrationProgress(progressSteps); + + // Wait for both to complete + const [migrationResult] = await Promise.all([migrationPromise, progressPromise]); + + migrationProgressManager.completeMigration(); + return migrationResult; + + } catch (error) { + console.error('Error executing comprehensive migration:', error); + migrationProgressManager.failMigration(error); + return false; + } + }, + + /** + * Simulate migration progress updates + */ + async simulateMigrationProgress(progressSteps) { + const totalSteps = progressSteps.length; + + for (let i = 0; i < progressSteps.length; i++) { + const step = progressSteps[i]; + const stepProgress = Math.round(((i + 1) / totalSteps) * 100); + + // Update progress for this step + migrationProgressManager.updateProgress({ + overallProgress: stepProgress, + currentStep: i + 1, + totalSteps, + stepName: step.step, + stepProgress: 0, + stepStatus: `Starting ${step.name}...`, + stepDetails: null, + boardId: Session.get('currentBoard') + }); + + // Simulate step progress + const stepDuration = step.duration; + const updateInterval = 100; // Update every 100ms + const totalUpdates = stepDuration / updateInterval; + + for (let j = 0; j < totalUpdates; j++) { + const stepStepProgress = Math.round(((j + 1) / totalUpdates) * 100); + + migrationProgressManager.updateProgress({ + overallProgress: stepProgress, + currentStep: i + 1, + totalSteps, + stepName: step.step, + stepProgress: stepStepProgress, + stepStatus: `Processing ${step.name}...`, + stepDetails: { progress: `${stepStepProgress}%` }, + boardId: Session.get('currentBoard') + }); + + await new Promise(resolve => setTimeout(resolve, updateInterval)); } - }); - }); -}); -Template.board.onDestroyed(function () { - // Clean up the subscription ready autorun to prevent memory leaks - if (this.subscriptionReadyAutorun) { - this.subscriptionReadyAutorun.stop(); - } -}); + // Complete the step + migrationProgressManager.updateProgress({ + overallProgress: stepProgress, + currentStep: i + 1, + totalSteps, + stepName: step.step, + stepProgress: 100, + stepStatus: `${step.name} completed`, + stepDetails: { status: 'completed' }, + boardId: Session.get('currentBoard') + }); + } + }, + + async startBackgroundMigration(boardId) { + try { + // Start background migration using the cron system + Meteor.call('boardMigration.startBoardMigration', boardId, (error, result) => { + if (error) { + console.error('Failed to start background migration:', error); + } else { + if (process.env.DEBUG === 'true') { + console.log('Background migration started for board:', boardId); + } + } + }); + } catch (error) { + console.error('Error starting background migration:', error); + } + }, + + async convertSharedListsToPerSwimlane(boardId) { + try { + const board = ReactiveCache.getBoard(boardId); + if (!board) return; + + // Check if board has already been processed for shared lists conversion + if (board.hasSharedListsConverted) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has already been processed for shared lists conversion`); + } + return; + } + + // Get all lists for this board + const allLists = board.lists(); + const swimlanes = board.swimlanes(); + + if (swimlanes.length === 0) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has no swimlanes, skipping shared lists conversion`); + } + return; + } + + // Find shared lists (lists with empty swimlaneId or null swimlaneId) + const sharedLists = allLists.filter(list => !list.swimlaneId || list.swimlaneId === ''); + + if (sharedLists.length === 0) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has no shared lists to convert`); + } + // Mark as processed even if no shared lists + Boards.update(boardId, { $set: { hasSharedListsConverted: true } }); + return; + } + + if (process.env.DEBUG === 'true') { + console.log(`Converting ${sharedLists.length} shared lists to per-swimlane lists for board ${boardId}`); + } + + // Convert each shared list to per-swimlane lists + for (const sharedList of sharedLists) { + // Create a copy of the list for each swimlane + for (const swimlane of swimlanes) { + // Check if this list already exists in this swimlane + const existingList = Lists.findOne({ + boardId: boardId, + swimlaneId: swimlane._id, + title: sharedList.title + }); + + if (!existingList) { + // Double-check to avoid race conditions + const doubleCheckList = ReactiveCache.getList({ + boardId: boardId, + swimlaneId: swimlane._id, + title: sharedList.title + }); + + if (!doubleCheckList) { + // Create a new list in this swimlane + const newListData = { + title: sharedList.title, + boardId: boardId, + swimlaneId: swimlane._id, + sort: sharedList.sort || 0, + archived: sharedList.archived || false, // Preserve archived state from original list + createdAt: new Date(), + modifiedAt: new Date() + }; + + // Copy other properties if they exist + if (sharedList.color) newListData.color = sharedList.color; + if (sharedList.wipLimit) newListData.wipLimit = sharedList.wipLimit; + if (sharedList.wipLimitEnabled) newListData.wipLimitEnabled = sharedList.wipLimitEnabled; + if (sharedList.wipLimitSoft) newListData.wipLimitSoft = sharedList.wipLimitSoft; + + Lists.insert(newListData); + + if (process.env.DEBUG === 'true') { + const archivedStatus = sharedList.archived ? ' (archived)' : ' (active)'; + console.log(`Created list "${sharedList.title}"${archivedStatus} for swimlane ${swimlane.title || swimlane._id}`); + } + } else { + if (process.env.DEBUG === 'true') { + console.log(`List "${sharedList.title}" already exists in swimlane ${swimlane.title || swimlane._id} (double-check), skipping`); + } + } + } else { + if (process.env.DEBUG === 'true') { + console.log(`List "${sharedList.title}" already exists in swimlane ${swimlane.title || swimlane._id}, skipping`); + } + } + } + + // Remove the original shared list completely + Lists.remove(sharedList._id); + + if (process.env.DEBUG === 'true') { + console.log(`Removed shared list "${sharedList.title}"`); + } + } + + // Mark board as processed + Boards.update(boardId, { $set: { hasSharedListsConverted: true } }); + + if (process.env.DEBUG === 'true') { + console.log(`Successfully converted ${sharedLists.length} shared lists to per-swimlane lists for board ${boardId}`); + } + + } catch (error) { + console.error('Error converting shared lists to per-swimlane:', error); + } + }, + + async fixMissingLists(boardId) { + try { + const board = ReactiveCache.getBoard(boardId); + if (!board) return; + + // Check if board has already been processed for missing lists fix + if (board.fixMissingListsCompleted) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has already been processed for missing lists fix`); + } + return; + } + + // Check if migration is needed + const needsMigration = await new Promise((resolve, reject) => { + Meteor.call('fixMissingListsMigration.needsMigration', boardId, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + + if (!needsMigration) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} does not need missing lists fix`); + } + return; + } + + if (process.env.DEBUG === 'true') { + console.log(`Starting fix missing lists migration for board ${boardId}`); + } + + // Execute the migration + const result = await new Promise((resolve, reject) => { + Meteor.call('fixMissingListsMigration.execute', boardId, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + + if (result && result.success) { + if (process.env.DEBUG === 'true') { + console.log(`Successfully fixed missing lists for board ${boardId}: created ${result.createdLists} lists, updated ${result.updatedCards} cards`); + } + } + + } catch (error) { + console.error('Error fixing missing lists:', error); + } + }, + + async fixDuplicateLists(boardId) { + try { + const board = ReactiveCache.getBoard(boardId); + if (!board) return; + + // Check if board has already been processed for duplicate lists fix + if (board.fixDuplicateListsCompleted) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has already been processed for duplicate lists fix`); + } + return; + } + + if (process.env.DEBUG === 'true') { + console.log(`Starting duplicate lists fix for board ${boardId}`); + } + + // Execute the duplicate lists fix + const result = await new Promise((resolve, reject) => { + Meteor.call('fixDuplicateLists.fixBoard', boardId, (error, result) => { + if (error) { + reject(error); + } else { + resolve(result); + } + }); + }); + + if (result && result.fixed > 0) { + if (process.env.DEBUG === 'true') { + console.log(`Successfully fixed ${result.fixed} duplicate lists for board ${boardId}: ${result.fixedSwimlanes} swimlanes, ${result.fixedLists} lists`); + } + + // Mark board as processed + Boards.update(boardId, { $set: { fixDuplicateListsCompleted: true } }); + } else if (process.env.DEBUG === 'true') { + console.log(`No duplicate lists found for board ${boardId}`); + // Still mark as processed to avoid repeated checks + Boards.update(boardId, { $set: { fixDuplicateListsCompleted: true } }); + } else { + // Still mark as processed to avoid repeated checks + Boards.update(boardId, { $set: { fixDuplicateListsCompleted: true } }); + } + + } catch (error) { + console.error('Error fixing duplicate lists:', error); + } + }, + + async startAttachmentMigrationIfNeeded(boardId) { + try { + // Check if board has already been migrated + if (attachmentMigrationManager.isBoardMigrated(boardId)) { + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has already been migrated, skipping`); + } + return; + } + + // Check if there are unconverted attachments + const unconvertedAttachments = attachmentMigrationManager.getUnconvertedAttachments(boardId); + + if (unconvertedAttachments.length > 0) { + if (process.env.DEBUG === 'true') { + console.log(`Starting attachment migration for ${unconvertedAttachments.length} attachments in board ${boardId}`); + } + await attachmentMigrationManager.startAttachmentMigration(boardId); + } else { + // No attachments to migrate, mark board as migrated + // This will be handled by the migration manager itself + if (process.env.DEBUG === 'true') { + console.log(`Board ${boardId} has no attachments to migrate`); + } + } + } catch (error) { + console.error('Error starting attachment migration:', error); + } + }, -Template.board.helpers({ onlyShowCurrentCard() { const isMiniScreen = Utils.isMiniScreen(); const currentCardId = Utils.getCurrentCardId(true); return isMiniScreen && currentCardId; }, - openCards() { - // In desktop mode, return array of all open cards - const isMobile = Utils.getMobileMode(); - if (!isMobile) { - const openCardIds = Session.get('openCards') || []; - return openCardIds - .map((id) => ReactiveCache.getCard(id)) - .filter((card) => card); - } - return []; - }, - goHome() { FlowRouter.go('home'); }, isConverting() { - return Template.instance().isConverting.get(); + return this.isConverting.get(); + }, + + isMigrating() { + return this.isMigrating.get(); }, isBoardReady() { - return Template.instance().isBoardReady.get(); + return this.isBoardReady.get(); }, currentBoard() { return Utils.getCurrentBoard(); }, -}); +}).register('board'); -Template.boardBody.onCreated(function () { - Meteor.subscribe('tableVisibilityModeSettings'); - this.showOverlay = new ReactiveVar(false); - this.draggingActive = new ReactiveVar(false); - this._isDragging = false; - // Used to set the overlay - this.mouseHasEnterCardDetails = false; - this._sortFieldsFixed = new Set(); // Track which boards have had sort fields fixed +BlazeComponent.extendComponent({ + onCreated() { + Meteor.subscribe('tableVisibilityModeSettings'); + this.showOverlay = new ReactiveVar(false); + this.draggingActive = new ReactiveVar(false); + this._isDragging = false; + // Used to set the overlay + this.mouseHasEnterCardDetails = false; + this._sortFieldsFixed = new Set(); // Track which boards have had sort fields fixed - // Store global reference for external access (swimlanes, cardDetails, etc.) - BoardBody = this; - - // Methods on the template instance for programmatic access - this.setIsDragging = (bool) => { - this.draggingActive.set(bool); - }; - - this.scrollLeft = (position = 0) => { - const swimlanes = this.$('.js-swimlanes'); - swimlanes && - swimlanes.animate({ - scrollLeft: position, - }); - }; - - this.scrollTop = (position = 0) => { - const swimlanes = this.$('.js-swimlanes'); - swimlanes && - swimlanes.animate({ - scrollTop: position, - }); - }; - - this.isViewSwimlanes = () => { - const currentUser = ReactiveCache.getCurrentUser(); - let boardView; - - if (currentUser) { - boardView = (currentUser.profile || {}).boardView; - } else { - boardView = window.localStorage.getItem('boardView'); - } - - // If no board view is set, default to swimlanes - if (!boardView) { - boardView = 'board-view-swimlanes'; - } - - return boardView === 'board-view-swimlanes'; - }; - - this.isViewLists = () => { - const currentUser = ReactiveCache.getCurrentUser(); - let boardView; - - if (currentUser) { - boardView = (currentUser.profile || {}).boardView; - } else { - boardView = window.localStorage.getItem('boardView'); - } - - return boardView === 'board-view-lists'; - }; - - // fix swimlanes sort field if there are null values - const currentBoardData = Utils.getCurrentBoard(); - if (currentBoardData && Swimlanes) { - const boardId = currentBoardData._id; - // Only fix sort fields once per board to prevent reactive loops - if (!this._sortFieldsFixed.has(`swimlanes-${boardId}`)) { - const nullSortSwimlanes = currentBoardData.nullSortSwimlanes(); - if (nullSortSwimlanes.length > 0) { - const swimlanes = currentBoardData.swimlanes(); - let count = 0; - swimlanes.forEach((s) => { - Swimlanes.update(s._id, { - $set: { - sort: count, - }, + // fix swimlanes sort field if there are null values + const currentBoardData = Utils.getCurrentBoard(); + if (currentBoardData && Swimlanes) { + const boardId = currentBoardData._id; + // Only fix sort fields once per board to prevent reactive loops + if (!this._sortFieldsFixed.has(`swimlanes-${boardId}`)) { + const nullSortSwimlanes = currentBoardData.nullSortSwimlanes(); + if (nullSortSwimlanes.length > 0) { + const swimlanes = currentBoardData.swimlanes(); + let count = 0; + swimlanes.forEach(s => { + Swimlanes.update(s._id, { + $set: { + sort: count, + }, + }); + count += 1; }); - count += 1; - }); - } - this._sortFieldsFixed.add(`swimlanes-${boardId}`); - } - } - - // fix lists sort field if there are null values - if (currentBoardData && Lists) { - const boardId = currentBoardData._id; - // Only fix sort fields once per board to prevent reactive loops - if (!this._sortFieldsFixed.has(`lists-${boardId}`)) { - const nullSortLists = currentBoardData.nullSortLists(); - if (nullSortLists.length > 0) { - const lists = currentBoardData.lists(); - let count = 0; - lists.forEach((l) => { - Lists.update(l._id, { - $set: { - sort: count, - }, - }); - count += 1; - }); - } - this._sortFieldsFixed.add(`lists-${boardId}`); - } - } -}); - -Template.boardBody.onRendered(function () { - // Initialize user settings (zoom and mobile mode) - Utils.initializeUserSettings(); - - // Detect iPhone devices and add class for better CSS targeting - const isIPhone = /iPhone|iPod/.test(navigator.userAgent); - if (isIPhone) { - document.body.classList.add('iphone-device'); - } - - // Accessibility: Focus management for popups and menus - function focusFirstInteractive(container) { - if (!container) return; - // Find first focusable element - const focusable = container.querySelectorAll( - 'button, [role="button"], a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])', - ); - for (let i = 0; i < focusable.length; i++) { - if (!focusable[i].disabled && focusable[i].offsetParent !== null) { - focusable[i].focus(); - break; - } - } - } - - // Observe for new popups/menus and set focus (but exclude swimlane content) - const popupObserver = new MutationObserver(function (mutations) { - mutations.forEach(function (mutation) { - mutation.addedNodes.forEach(function (node) { - if ( - node.nodeType === 1 && - (node.classList.contains('popup') || - node.classList.contains('modal') || - node.classList.contains('menu')) && - !node.closest('.js-swimlanes') && - !node.closest('.swimlane') && - !node.closest('.list') && - !node.closest('.minicard') - ) { - setTimeout(function () { - focusFirstInteractive(node); - }, 10); } + this._sortFieldsFixed.add(`swimlanes-${boardId}`); + } + } + + // fix lists sort field if there are null values + if (currentBoardData && Lists) { + const boardId = currentBoardData._id; + // Only fix sort fields once per board to prevent reactive loops + if (!this._sortFieldsFixed.has(`lists-${boardId}`)) { + const nullSortLists = currentBoardData.nullSortLists(); + if (nullSortLists.length > 0) { + const lists = currentBoardData.lists(); + let count = 0; + lists.forEach(l => { + Lists.update(l._id, { + $set: { + sort: count, + }, + }); + count += 1; + }); + } + this._sortFieldsFixed.add(`lists-${boardId}`); + } + } + }, + onRendered() { + // Initialize user settings (zoom and mobile mode) + Utils.initializeUserSettings(); + + // Detect iPhone devices and add class for better CSS targeting + const isIPhone = /iPhone|iPod/.test(navigator.userAgent); + if (isIPhone) { + document.body.classList.add('iphone-device'); + } + + // Accessibility: Focus management for popups and menus + function focusFirstInteractive(container) { + if (!container) return; + // Find first focusable element + const focusable = container.querySelectorAll('button, [role="button"], a[href], input, select, textarea, [tabindex]:not([tabindex="-1"])'); + for (let i = 0; i < focusable.length; i++) { + if (!focusable[i].disabled && focusable[i].offsetParent !== null) { + focusable[i].focus(); + break; + } + } + } + + // Observe for new popups/menus and set focus (but exclude swimlane content) + const popupObserver = new MutationObserver(function(mutations) { + mutations.forEach(function(mutation) { + mutation.addedNodes.forEach(function(node) { + if (node.nodeType === 1 && + (node.classList.contains('popup') || node.classList.contains('modal') || node.classList.contains('menu')) && + !node.closest('.js-swimlanes') && + !node.closest('.swimlane') && + !node.closest('.list') && + !node.closest('.minicard')) { + setTimeout(function() { focusFirstInteractive(node); }, 10); + } + }); }); }); - }); - popupObserver.observe(document.body, { childList: true, subtree: true }); + popupObserver.observe(document.body, { childList: true, subtree: true }); - // Remove tabindex from non-interactive elements (e.g., user abbreviations, labels) - document - .querySelectorAll( - '.user-abbreviation, .user-label, .card-header-label, .edit-label, .private-label', - ) - .forEach(function (el) { + // Remove tabindex from non-interactive elements (e.g., user abbreviations, labels) + document.querySelectorAll('.user-abbreviation, .user-label, .card-header-label, .edit-label, .private-label').forEach(function(el) { if (el.hasAttribute('tabindex')) { el.removeAttribute('tabindex'); } }); - /* - // Add a toggle button for keyboard shortcuts accessibility - if (!document.getElementById('wekan-shortcuts-toggle')) { - const toggleContainer = document.createElement('div'); - toggleContainer.id = 'wekan-shortcuts-toggle'; - toggleContainer.style.position = 'fixed'; - toggleContainer.style.top = '10px'; - toggleContainer.style.right = '10px'; - toggleContainer.style.zIndex = '1000'; - toggleContainer.style.background = '#fff'; - toggleContainer.style.border = '2px solid #005fcc'; - toggleContainer.style.borderRadius = '6px'; - toggleContainer.style.padding = '8px 12px'; - toggleContainer.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)'; - toggleContainer.style.fontSize = '16px'; - toggleContainer.style.color = '#005fcc'; - toggleContainer.setAttribute('role', 'region'); - toggleContainer.setAttribute('aria-label', 'Keyboard Shortcuts Settings'); - toggleContainer.innerHTML = ` - - `; - document.body.appendChild(toggleContainer); - const checkbox = document.getElementById('shortcuts-toggle-checkbox'); - checkbox.addEventListener('change', function(e) { - window.toggleWekanShortcuts(e.target.checked); - }); - } - */ - // Ensure toggle-buttons, color choices, reactions, renaming, and calendar controls are focusable and have ARIA roles - document.querySelectorAll('.js-toggle').forEach(function (el) { - el.setAttribute('tabindex', '0'); - el.setAttribute('role', 'button'); - // Short, descriptive label for favorite/star toggle - if (el.classList.contains('js-favorite-toggle')) { - el.setAttribute('aria-label', TAPi18n.__('favorite-toggle-label')); - } else { - el.setAttribute('aria-label', 'Toggle'); - } - }); - document.querySelectorAll('.js-color-choice').forEach(function (el) { - el.setAttribute('tabindex', '0'); - el.setAttribute('role', 'button'); - el.setAttribute('aria-label', 'Choose color'); - }); - document.querySelectorAll('.js-reaction').forEach(function (el) { - el.setAttribute('tabindex', '0'); - el.setAttribute('role', 'button'); - el.setAttribute('aria-label', 'React'); - }); - document.querySelectorAll('.js-rename-swimlane').forEach(function (el) { - el.setAttribute('tabindex', '0'); - el.setAttribute('role', 'button'); - el.setAttribute('aria-label', 'Rename swimlane'); - }); - document.querySelectorAll('.js-rename-list').forEach(function (el) { - el.setAttribute('tabindex', '0'); - el.setAttribute('role', 'button'); - el.setAttribute('aria-label', 'Rename list'); - }); - document.querySelectorAll('.fc-button').forEach(function (el) { - el.setAttribute('tabindex', '0'); - el.setAttribute('role', 'button'); - }); - // Set the language attribute on the element for accessibility - document.documentElement.lang = TAPi18n.getLanguage(); - - // Ensure the accessible name for the board view switcher matches the visible label "Swimlanes" - // This fixes WCAG 2.5.3: Label in Name - const swimlanesSwitcher = this.$('.js-board-view-swimlanes'); - if (swimlanesSwitcher.length) { - swimlanesSwitcher.attr( - 'aria-label', - swimlanesSwitcher.text().trim() || 'Swimlanes', - ); - } - - // Add a highly visible focus indicator and improve contrast for interactive elements - if (!document.getElementById('wekan-accessible-focus-style')) { - const style = document.createElement('style'); - style.id = 'wekan-accessible-focus-style'; - style.innerHTML = ` - /* Focus indicator */ - button:focus, [role="button"]:focus, a:focus, input:focus, select:focus, textarea:focus, .dropdown-menu:focus, .js-board-view-swimlanes:focus, .js-add-card:focus { - outline: 3px solid #005fcc !important; - outline-offset: 2px !important; - } - /* Input borders */ - input, textarea, select { - border: 2px solid #222 !important; - } - /* Plus icon for adding a new card */ - .js-add-card { - color: #005fcc !important; /* dark blue for contrast */ - cursor: pointer; - outline: none; - } - .js-add-card[tabindex] { - outline: none; - } - /* Sidebar hamburger menu button in header */ - .js-toggle-sidebar .fa-bars { - color: #fff !important; - } - /* Grey icons in card detail header */ - .card-detail-header .fa, .card-detail-header .icon { - color: #444 !important; - } - /* Grey operating elements in card detail */ - .card-detail .fa, .card-detail .icon { - color: #444 !important; - } - /* Blue bar in checklists */ - .checklist-progress-bar { - background-color: #005fcc !important; - } - /* Green checkmark in checklists */ - .checklist .fa-check { - color: #007a33 !important; - } - /* X-Button and arrow button in menus */ - .close, .fa-arrow-left, .icon-arrow-left { - color: #005fcc !important; - } - /* Cross icon to move boards */ - .js-move-board { - color: #005fcc !important; - } - /* Current date background */ - .current-date { - background-color: #005fcc !important; - color: #fff !important; - } - `; - document.head.appendChild(style); - } - // Ensure plus/add elements are focusable and have ARIA roles - document.querySelectorAll('.js-add-card').forEach(function (el) { - el.setAttribute('tabindex', '0'); - el.setAttribute('role', 'button'); - el.setAttribute('aria-label', 'Add new card'); - }); - - const tpl = this; - const $swimlanesDom = tpl.$('.js-swimlanes'); - - $swimlanesDom.sortable({ - tolerance: 'pointer', - appendTo: '.board-canvas', - helper(evt, item) { - const helper = $(`
`); - helper.append(item.clone()); - // Also grab the list of lists of cards - const list = item.next(); - helper.append(list.clone()); - return helper; - }, - items: '.swimlane:not(.placeholder)', - placeholder: 'swimlane placeholder', - distance: 7, - start(evt, ui) { - const listDom = ui.placeholder.next('.js-swimlane'); - const parentOffset = ui.item.parent().offset(); - - ui.placeholder.height(ui.helper.height()); - EscapeActions.executeUpTo('popup-close'); - listDom.addClass('moving-swimlane'); - tpl.setIsDragging(true); - - ui.placeholder.insertAfter(ui.placeholder.next()); - tpl.origPlaceholderIndex = ui.placeholder.index(); - - // resize all swimlanes + headers to be a total of 150 px per row - // this could be achieved by setIsDragging(true) but we want immediate - // result - ui.item - .siblings('.js-swimlane') - .css('height', `${swimlaneWhileSortingHeight - 26}px`); - - // set the new scroll height after the resize and insertion of - // the placeholder. We want the element under the cursor to stay - // at the same place on the screen - ui.item.parent().get(0).scrollTop = - ui.placeholder.get(0).offsetTop + parentOffset.top - evt.pageY; - }, - beforeStop(evt, ui) { - const parentOffset = ui.item.parent().offset(); - const siblings = ui.item.siblings('.js-swimlane'); - siblings.css('height', ''); - - // compute the new scroll height after the resize and removal of - // the placeholder - const scrollTop = - ui.placeholder.get(0).offsetTop + parentOffset.top - evt.pageY; - - // then reset the original view of the swimlane - siblings.removeClass('moving-swimlane'); - - // and apply the computed scrollheight - ui.item.parent().get(0).scrollTop = scrollTop; - }, - stop(evt, ui) { - // To attribute the new index number, we need to get the DOM element - // of the previous and the following card -- if any. - const prevSwimlaneDom = ui.item.prevAll('.js-swimlane').get(0); - const nextSwimlaneDom = ui.item.nextAll('.js-swimlane').get(0); - const sortIndex = calculateIndex(prevSwimlaneDom, nextSwimlaneDom, 1); - - $swimlanesDom.sortable('cancel'); - const swimlaneDomElement = ui.item.get(0); - const swimlane = Blaze.getData(swimlaneDomElement); - - Swimlanes.update(swimlane._id, { - $set: { - sort: sortIndex.base, - }, + /* + // Add a toggle button for keyboard shortcuts accessibility + if (!document.getElementById('wekan-shortcuts-toggle')) { + const toggleContainer = document.createElement('div'); + toggleContainer.id = 'wekan-shortcuts-toggle'; + toggleContainer.style.position = 'fixed'; + toggleContainer.style.top = '10px'; + toggleContainer.style.right = '10px'; + toggleContainer.style.zIndex = '1000'; + toggleContainer.style.background = '#fff'; + toggleContainer.style.border = '2px solid #005fcc'; + toggleContainer.style.borderRadius = '6px'; + toggleContainer.style.padding = '8px 12px'; + toggleContainer.style.boxShadow = '0 2px 8px rgba(0,0,0,0.1)'; + toggleContainer.style.fontSize = '16px'; + toggleContainer.style.color = '#005fcc'; + toggleContainer.setAttribute('role', 'region'); + toggleContainer.setAttribute('aria-label', 'Keyboard Shortcuts Settings'); + toggleContainer.innerHTML = ` + + `; + document.body.appendChild(toggleContainer); + const checkbox = document.getElementById('shortcuts-toggle-checkbox'); + checkbox.addEventListener('change', function(e) { + window.toggleWekanShortcuts(e.target.checked); }); - - tpl.setIsDragging(false); - }, - sort(evt, ui) { - // get the mouse position in the sortable - const parentOffset = ui.item.parent().offset(); - const cursorY = - evt.pageY - parentOffset.top + ui.item.parent().scrollTop(); - - // compute the intended index of the placeholder (we need to skip the - // slots between the headers and the list of cards) - const newplaceholderIndex = Math.floor( - cursorY / swimlaneWhileSortingHeight, - ); - let destPlaceholderIndex = (newplaceholderIndex + 1) * 2; - - // if we are scrolling far away from the bottom of the list - if (destPlaceholderIndex >= ui.item.parent().get(0).childElementCount) { - destPlaceholderIndex = ui.item.parent().get(0).childElementCount - 1; + } + */ + // Ensure toggle-buttons, color choices, reactions, renaming, and calendar controls are focusable and have ARIA roles + document.querySelectorAll('.js-toggle').forEach(function(el) { + el.setAttribute('tabindex', '0'); + el.setAttribute('role', 'button'); + // Short, descriptive label for favorite/star toggle + if (el.classList.contains('js-favorite-toggle')) { + el.setAttribute('aria-label', TAPi18n.__('favorite-toggle-label')); + } else { + el.setAttribute('aria-label', 'Toggle'); } + }); + document.querySelectorAll('.js-color-choice').forEach(function(el) { + el.setAttribute('tabindex', '0'); + el.setAttribute('role', 'button'); + el.setAttribute('aria-label', 'Choose color'); + }); + document.querySelectorAll('.js-reaction').forEach(function(el) { + el.setAttribute('tabindex', '0'); + el.setAttribute('role', 'button'); + el.setAttribute('aria-label', 'React'); + }); + document.querySelectorAll('.js-rename-swimlane').forEach(function(el) { + el.setAttribute('tabindex', '0'); + el.setAttribute('role', 'button'); + el.setAttribute('aria-label', 'Rename swimlane'); + }); + document.querySelectorAll('.js-rename-list').forEach(function(el) { + el.setAttribute('tabindex', '0'); + el.setAttribute('role', 'button'); + el.setAttribute('aria-label', 'Rename list'); + }); + document.querySelectorAll('.fc-button').forEach(function(el) { + el.setAttribute('tabindex', '0'); + el.setAttribute('role', 'button'); + }); + // Set the language attribute on the element for accessibility + document.documentElement.lang = TAPi18n.getLanguage(); - // update the placeholder position in the DOM tree - if (destPlaceholderIndex !== ui.placeholder.index()) { - if (destPlaceholderIndex < tpl.origPlaceholderIndex) { - ui.placeholder.insertBefore( - ui.placeholder - .siblings() - .slice(destPlaceholderIndex - 2, destPlaceholderIndex - 1), - ); - } else { - ui.placeholder.insertAfter( - ui.placeholder - .siblings() - .slice(destPlaceholderIndex - 1, destPlaceholderIndex), - ); + // Ensure the accessible name for the board view switcher matches the visible label "Swimlanes" + // This fixes WCAG 2.5.3: Label in Name + const swimlanesSwitcher = this.$('.js-board-view-swimlanes'); + if (swimlanesSwitcher.length) { + swimlanesSwitcher.attr('aria-label', swimlanesSwitcher.text().trim() || 'Swimlanes'); + } + + // Add a highly visible focus indicator and improve contrast for interactive elements + if (!document.getElementById('wekan-accessible-focus-style')) { + const style = document.createElement('style'); + style.id = 'wekan-accessible-focus-style'; + style.innerHTML = ` + /* Focus indicator */ + button:focus, [role="button"]:focus, a:focus, input:focus, select:focus, textarea:focus, .dropdown-menu:focus, .js-board-view-swimlanes:focus, .js-add-card:focus { + outline: 3px solid #005fcc !important; + outline-offset: 2px !important; } - } - }, - }); + /* Input borders */ + input, textarea, select { + border: 2px solid #222 !important; + } + /* Plus icon for adding a new card */ + .js-add-card { + color: #005fcc !important; /* dark blue for contrast */ + cursor: pointer; + outline: none; + } + .js-add-card[tabindex] { + outline: none; + } + /* Hamburger menu */ + .fa-bars, .icon-hamburger { + color: #222 !important; + } + /* Grey icons in card detail header */ + .card-detail-header .fa, .card-detail-header .icon { + color: #444 !important; + } + /* Grey operating elements in card detail */ + .card-detail .fa, .card-detail .icon { + color: #444 !important; + } + /* Blue bar in checklists */ + .checklist-progress-bar { + background-color: #005fcc !important; + } + /* Green checkmark in checklists */ + .checklist .fa-check { + color: #007a33 !important; + } + /* X-Button and arrow button in menus */ + .close, .fa-arrow-left, .icon-arrow-left { + color: #005fcc !important; + } + /* Cross icon to move boards */ + .js-move-board { + color: #005fcc !important; + } + /* Current date background */ + .current-date { + background-color: #005fcc !important; + color: #fff !important; + } + `; + document.head.appendChild(style); + } + // Ensure plus/add elements are focusable and have ARIA roles + document.querySelectorAll('.js-add-card').forEach(function(el) { + el.setAttribute('tabindex', '0'); + el.setAttribute('role', 'button'); + el.setAttribute('aria-label', 'Add new card'); + }); - this.autorun(() => { - // Always reset dragscroll on view switch - dragscroll.reset(); + const boardComponent = this; + const $swimlanesDom = boardComponent.$('.js-swimlanes'); - if ($swimlanesDom.data('uiSortable') || $swimlanesDom.data('sortable')) { - if (Utils.isTouchScreenOrShowDesktopDragHandles()) { + $swimlanesDom.sortable({ + tolerance: 'pointer', + appendTo: '.board-canvas', + helper(evt, item) { + const helper = $(`
`); + helper.append(item.clone()); + // Also grab the list of lists of cards + const list = item.next(); + helper.append(list.clone()); + return helper; + }, + items: '.swimlane:not(.placeholder)', + placeholder: 'swimlane placeholder', + distance: 7, + start(evt, ui) { + const listDom = ui.placeholder.next('.js-swimlane'); + const parentOffset = ui.item.parent().offset(); + + ui.placeholder.height(ui.helper.height()); + EscapeActions.executeUpTo('popup-close'); + listDom.addClass('moving-swimlane'); + boardComponent.setIsDragging(true); + + ui.placeholder.insertAfter(ui.placeholder.next()); + boardComponent.origPlaceholderIndex = ui.placeholder.index(); + + // resize all swimlanes + headers to be a total of 150 px per row + // this could be achieved by setIsDragging(true) but we want immediate + // result + ui.item + .siblings('.js-swimlane') + .css('height', `${swimlaneWhileSortingHeight - 26}px`); + + // set the new scroll height after the resize and insertion of + // the placeholder. We want the element under the cursor to stay + // at the same place on the screen + ui.item.parent().get(0).scrollTop = + ui.placeholder.get(0).offsetTop + parentOffset.top - evt.pageY; + }, + beforeStop(evt, ui) { + const parentOffset = ui.item.parent().offset(); + const siblings = ui.item.siblings('.js-swimlane'); + siblings.css('height', ''); + + // compute the new scroll height after the resize and removal of + // the placeholder + const scrollTop = + ui.placeholder.get(0).offsetTop + parentOffset.top - evt.pageY; + + // then reset the original view of the swimlane + siblings.removeClass('moving-swimlane'); + + // and apply the computed scrollheight + ui.item.parent().get(0).scrollTop = scrollTop; + }, + stop(evt, ui) { + // To attribute the new index number, we need to get the DOM element + // of the previous and the following card -- if any. + const prevSwimlaneDom = ui.item.prevAll('.js-swimlane').get(0); + const nextSwimlaneDom = ui.item.nextAll('.js-swimlane').get(0); + const sortIndex = calculateIndex(prevSwimlaneDom, nextSwimlaneDom, 1); + + $swimlanesDom.sortable('cancel'); + const swimlaneDomElement = ui.item.get(0); + const swimlane = Blaze.getData(swimlaneDomElement); + + Swimlanes.update(swimlane._id, { + $set: { + sort: sortIndex.base, + }, + }); + + boardComponent.setIsDragging(false); + }, + sort(evt, ui) { + // get the mouse position in the sortable + const parentOffset = ui.item.parent().offset(); + const cursorY = + evt.pageY - parentOffset.top + ui.item.parent().scrollTop(); + + // compute the intended index of the placeholder (we need to skip the + // slots between the headers and the list of cards) + const newplaceholderIndex = Math.floor( + cursorY / swimlaneWhileSortingHeight, + ); + let destPlaceholderIndex = (newplaceholderIndex + 1) * 2; + + // if we are scrolling far away from the bottom of the list + if (destPlaceholderIndex >= ui.item.parent().get(0).childElementCount) { + destPlaceholderIndex = ui.item.parent().get(0).childElementCount - 1; + } + + // update the placeholder position in the DOM tree + if (destPlaceholderIndex !== ui.placeholder.index()) { + if (destPlaceholderIndex < boardComponent.origPlaceholderIndex) { + ui.placeholder.insertBefore( + ui.placeholder + .siblings() + .slice(destPlaceholderIndex - 2, destPlaceholderIndex - 1), + ); + } else { + ui.placeholder.insertAfter( + ui.placeholder + .siblings() + .slice(destPlaceholderIndex - 1, destPlaceholderIndex), + ); + } + } + }, + }); + + this.autorun(() => { + // Always reset dragscroll on view switch + dragscroll.reset(); + + if ($swimlanesDom.data('uiSortable') || $swimlanesDom.data('sortable')) { + if (Utils.isTouchScreenOrShowDesktopDragHandles()) { + $swimlanesDom.sortable('option', 'handle', '.js-swimlane-header-handle'); + } else { + $swimlanesDom.sortable('option', 'handle', '.swimlane-header'); + } + + // Disable drag-dropping if the current user is not a board member $swimlanesDom.sortable( 'option', - 'handle', - '.js-swimlane-header-handle', + 'disabled', + !ReactiveCache.getCurrentUser()?.isBoardAdmin(), ); - } else { - $swimlanesDom.sortable('option', 'handle', '.swimlane-header'); } + }); - // Disable drag-dropping if the current user is not a board member - $swimlanesDom.sortable( - 'option', - 'disabled', - !ReactiveCache.getCurrentUser()?.isBoardAdmin(), - ); + // If there is no data in the board (ie, no lists) we autofocus the list + // creation form by clicking on the corresponding element. + const currentBoard = Utils.getCurrentBoard(); + if (Utils.canModifyBoard() && currentBoard.lists().length === 0) { + boardComponent.openNewListForm(); } - }); - dragscroll.reset(); - Utils.setBackgroundImage(); -}); - -Template.boardBody.onDestroyed(function () { - if (BoardBody === this) { - BoardBody = null; - } -}); - -Template.boardBody.helpers({ - draggingActive() { - return Template.instance().draggingActive.get(); - }, - showOverlay() { - return Template.instance().showOverlay.get(); + dragscroll.reset(); + Utils.setBackgroundImage(); }, + notDisplayThisBoard() { - let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne( - 'tableVisibilityMode-allowPrivateOnly', - ); + let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly'); let currentBoard = Utils.getCurrentBoard(); - return ( - allowPrivateVisibilityOnly !== undefined && - allowPrivateVisibilityOnly.booleanValue && - currentBoard && - currentBoard.permission == 'public' - ); + return allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue && currentBoard && currentBoard.permission == 'public'; }, isViewSwimlanes() { const currentUser = ReactiveCache.getCurrentUser(); let boardView; - + if (currentUser) { boardView = (currentUser.profile || {}).boardView; } else { boardView = window.localStorage.getItem('boardView'); } - + // If no board view is set, default to swimlanes if (!boardView) { boardView = 'board-view-swimlanes'; } - + return boardView === 'board-view-swimlanes'; }, isViewLists() { const currentUser = ReactiveCache.getCurrentUser(); let boardView; - + if (currentUser) { boardView = (currentUser.profile || {}).boardView; } else { boardView = window.localStorage.getItem('boardView'); } - + return boardView === 'board-view-lists'; }, isViewCalendar() { const currentUser = ReactiveCache.getCurrentUser(); let boardView; - + if (currentUser) { boardView = (currentUser.profile || {}).boardView; } else { boardView = window.localStorage.getItem('boardView'); } - + return boardView === 'board-view-cal'; }, - isViewGantt() { - const currentUser = ReactiveCache.getCurrentUser(); - let boardView; - - if (currentUser) { - boardView = (currentUser.profile || {}).boardView; - } else { - boardView = window.localStorage.getItem('boardView'); - } - - return boardView === 'board-view-gantt'; - }, - hasSwimlanes() { const currentBoard = Utils.getCurrentBoard(); if (!currentBoard) { @@ -698,16 +1002,12 @@ Template.boardBody.helpers({ } return false; } - + try { const swimlanes = currentBoard.swimlanes(); const hasSwimlanes = swimlanes && swimlanes.length > 0; if (process.env.DEBUG === 'true') { - console.log( - 'hasSwimlanes: Board has', - swimlanes ? swimlanes.length : 0, - 'swimlanes', - ); + console.log('hasSwimlanes: Board has', swimlanes ? swimlanes.length : 0, 'swimlanes'); } return hasSwimlanes; } catch (error) { @@ -716,6 +1016,7 @@ Template.boardBody.helpers({ } }, + isVerticalScrollbars() { const user = ReactiveCache.getCurrentUser(); return user && user.isVerticalScrollbars(); @@ -734,92 +1035,118 @@ Template.boardBody.helpers({ debugBoardStateData() { const currentBoard = Utils.getCurrentBoard(); const currentBoardId = Session.get('currentBoard'); - const tpl = Template.instance(); - const isBoardReady = tpl.isBoardReady.get(); - const isConverting = tpl.isConverting.get(); + const isBoardReady = this.isBoardReady.get(); + const isConverting = this.isConverting.get(); + const isMigrating = this.isMigrating.get(); const boardView = Utils.boardView(); - + if (process.env.DEBUG === 'true') { console.log('=== BOARD DEBUG STATE ==='); console.log('currentBoardId:', currentBoardId); - console.log( - 'currentBoard:', - !!currentBoard, - currentBoard ? currentBoard.title : 'none', - ); + console.log('currentBoard:', !!currentBoard, currentBoard ? currentBoard.title : 'none'); console.log('isBoardReady:', isBoardReady); console.log('isConverting:', isConverting); + console.log('isMigrating:', isMigrating); console.log('boardView:', boardView); console.log('========================'); } - + return { currentBoardId, hasCurrentBoard: !!currentBoard, currentBoardTitle: currentBoard ? currentBoard.title : 'none', isBoardReady, isConverting, - boardView, + isMigrating, + boardView }; }, -}); -Template.boardBody.events({ - // XXX The board-overlay div should probably be moved to the parent - // component. - mouseup(event, tpl) { - if (tpl._isDragging) { - tpl._isDragging = false; + + openNewListForm() { + if (this.isViewSwimlanes()) { + // The form had been removed in 416b17062e57f215206e93a85b02ef9eb1ab4902 + // this.childComponents('swimlane')[0] + // .childComponents('addListAndSwimlaneForm')[0] + // .open(); + } else if (this.isViewLists()) { + this.childComponents('listsGroup')[0] + .childComponents('addListForm')[0] + .open(); } }, - 'click .js-empty-board-add-swimlane': Popup.open('swimlaneAdd'), - // Global drag and drop file upload handlers for better visual feedback - 'dragover .board-canvas'(event) { - const dataTransfer = event.originalEvent.dataTransfer; - if ( - dataTransfer && - dataTransfer.types && - dataTransfer.types.includes('Files') - ) { - event.preventDefault(); - // Add visual indicator that files can be dropped - $('.board-canvas').addClass('file-drag-over'); - } + events() { + return [ + { + // XXX The board-overlay div should probably be moved to the parent + // component. + mouseup() { + if (this._isDragging) { + this._isDragging = false; + } + }, + 'click .js-empty-board-add-swimlane': Popup.open('swimlaneAdd'), + // Global drag and drop file upload handlers for better visual feedback + 'dragover .board-canvas'(event) { + const dataTransfer = event.originalEvent.dataTransfer; + if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { + event.preventDefault(); + // Add visual indicator that files can be dropped + $('.board-canvas').addClass('file-drag-over'); + } + }, + 'dragleave .board-canvas'(event) { + const dataTransfer = event.originalEvent.dataTransfer; + if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { + // Only remove class if we're leaving the board canvas entirely + if (!event.currentTarget.contains(event.relatedTarget)) { + $('.board-canvas').removeClass('file-drag-over'); + } + } + }, + 'drop .board-canvas'(event) { + const dataTransfer = event.originalEvent.dataTransfer; + if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { + event.preventDefault(); + $('.board-canvas').removeClass('file-drag-over'); + } + }, + }, + ]; }, - 'dragleave .board-canvas'(event) { - const dataTransfer = event.originalEvent.dataTransfer; - if ( - dataTransfer && - dataTransfer.types && - dataTransfer.types.includes('Files') - ) { - // Only remove class if we're leaving the board canvas entirely - if (!event.currentTarget.contains(event.relatedTarget)) { - $('.board-canvas').removeClass('file-drag-over'); - } - } + + // XXX Flow components allow us to avoid creating these two setter methods by + // exposing a public API to modify the component state. We need to investigate + // best practices here. + setIsDragging(bool) { + this.draggingActive.set(bool); }, - 'drop .board-canvas'(event) { - const dataTransfer = event.originalEvent.dataTransfer; - if ( - dataTransfer && - dataTransfer.types && - dataTransfer.types.includes('Files') - ) { - event.preventDefault(); - $('.board-canvas').removeClass('file-drag-over'); - } + + scrollLeft(position = 0) { + const swimlanes = this.$('.js-swimlanes'); + swimlanes && + swimlanes.animate({ + scrollLeft: position, + }); }, -}); + + scrollTop(position = 0) { + const swimlanes = this.$('.js-swimlanes'); + swimlanes && + swimlanes.animate({ + scrollTop: position, + }); + }, +}).register('boardBody'); // Accessibility: Allow users to enable/disable keyboard shortcuts window.wekanShortcutsEnabled = true; -window.toggleWekanShortcuts = function (enabled) { +window.toggleWekanShortcuts = function(enabled) { window.wekanShortcutsEnabled = !!enabled; }; // Example: Wrap your character key shortcut handler like this -document.addEventListener('keydown', function (e) { +document.addEventListener('keydown', function(e) { // Example: "W" key shortcut (replace with your actual shortcut logic) if (!window.wekanShortcutsEnabled) return; if (e.key === 'w' || e.key === 'W') { @@ -829,7 +1156,7 @@ document.addEventListener('keydown', function (e) { }); // Keyboard accessibility for card actions (favorite, archive, duplicate, etc.) -document.addEventListener('keydown', function (e) { +document.addEventListener('keydown', function(e) { if (!window.wekanShortcutsEnabled) return; // Only proceed if focus is on a card action element const active = document.activeElement; @@ -874,20 +1201,14 @@ document.addEventListener('keydown', function (e) { } } } - // Ensure move card buttons are focusable and have ARIA roles - document.querySelectorAll('.js-move-card').forEach(function (el) { - el.setAttribute('tabindex', '0'); - el.setAttribute('role', 'button'); - el.setAttribute('aria-label', 'Move card'); - }); + // Ensure move card buttons are focusable and have ARIA roles + document.querySelectorAll('.js-move-card').forEach(function(el) { + el.setAttribute('tabindex', '0'); + el.setAttribute('role', 'button'); + el.setAttribute('aria-label', 'Move card'); + }); // Make toggle-buttons, color choices, reactions, and X-buttons keyboard accessible - if ( - active && - (active.classList.contains('js-toggle') || - active.classList.contains('js-color-choice') || - active.classList.contains('js-reaction') || - active.classList.contains('close')) - ) { + if (active && (active.classList.contains('js-toggle') || active.classList.contains('js-color-choice') || active.classList.contains('js-reaction') || active.classList.contains('close'))) { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); active.click(); @@ -895,21 +1216,13 @@ document.addEventListener('keydown', function (e) { } // Prevent scripts from removing focus when received if (active) { - active.addEventListener( - 'focus', - function (e) { - // Do not remove focus - // No-op: This prevents F55 failure - }, - { once: true }, - ); + active.addEventListener('focus', function(e) { + // Do not remove focus + // No-op: This prevents F55 failure + }, { once: true }); } // Make swimlane/list renaming keyboard accessible - if ( - active && - (active.classList.contains('js-rename-swimlane') || - active.classList.contains('js-rename-list')) - ) { + if (active && (active.classList.contains('js-rename-swimlane') || active.classList.contains('js-rename-list'))) { if (e.key === 'Enter') { e.preventDefault(); active.click(); @@ -924,59 +1237,37 @@ document.addEventListener('keydown', function (e) { } }); -Template.calendarView.onRendered(function () { - // Set the language attribute on the element for accessibility - document.documentElement.lang = TAPi18n.getLanguage(); +BlazeComponent.extendComponent({ + onRendered() { + // Set the language attribute on the element for accessibility + document.documentElement.lang = TAPi18n.getLanguage(); - this.autorun(function () { - const calendarEl = document.getElementById('calendar-view'); - if (calendarEl && calendarEl._wekanCalendar) { - calendarEl._wekanCalendar.refetchEvents(); - } - }); -}); - -Template.calendarView.helpers({ + this.autorun(function () { + $('#calendar-view').fullCalendar('refetchEvents'); + }); + }, calendarOptions() { - const t = (key, fallback) => { - const translated = TAPi18n.__(key); - return translated && translated !== key ? translated : fallback; - }; - return { id: 'calendar-view', - initialView: 'dayGridMonth', + defaultView: 'month', editable: true, selectable: true, + timezone: 'local', weekNumbers: true, - // Use non-localized AM/PM time format to avoid confusing notations like 上/下/中 - // Use full 'am'/'pm' instead of single-letter 'a'/'p' for clarity - eventTimeFormat: { - hour: 'numeric', - minute: '2-digit', - meridiem: 'short', - }, - slotLabelFormat: { - hour: 'numeric', - minute: '2-digit', - meridiem: 'short', - }, - headerToolbar: { - left: 'title today prev,next', + header: { + left: 'title today prev,next', center: - 'timeGridDay,listDay timeGridWeek,listWeek dayGridMonth,listMonth', + 'agendaDay,listDay,timelineDay agendaWeek,listWeek,timelineWeek month,listMonth', right: '', }, - buttonIcons: false, - buttonText: { - prev: t('previous', 'Previous'), - next: t('next', 'Next'), - today: t('today', 'Today'), - day: t('day', 'Day'), - week: t('week', 'Week'), - month: t('month', 'Month'), - list: t('list', 'List'), - }, + buttonText: { + prev: TAPi18n.__('calendar-previous-month-label'), // e.g. "Previous month" + next: TAPi18n.__('calendar-next-month-label'), // e.g. "Next month" + }, + ariaLabel: { + prev: TAPi18n.__('calendar-previous-month-label'), + next: TAPi18n.__('calendar-next-month-label'), + }, // height: 'parent', nope, doesn't work as the parent might be small height: 'auto', /* TODO: lists as resources: https://fullcalendar.io/docs/vertical-resource-view */ @@ -984,12 +1275,12 @@ Template.calendarView.helpers({ nowIndicator: true, businessHours: { // days of week. an array of zero-based day of week integers (0=Sunday) - daysOfWeek: [1, 2, 3, 4, 5], // Monday - Friday + dow: [1, 2, 3, 4, 5], // Monday - Friday start: '8:00', end: '18:00', }, locale: TAPi18n.getLanguage(), - events(fetchInfo, callback) { + events(start, end, timezone, callback) { const currentBoard = Utils.getCurrentBoard(); const events = []; const pushEvent = function (card, title, start, end, extraCls) { @@ -1015,12 +1306,12 @@ Template.calendarView.helpers({ }); }; currentBoard - .cardsInInterval(fetchInfo.start, fetchInfo.end) + .cardsInInterval(start.toDate(), end.toDate()) .forEach(function (card) { pushEvent(card); }); currentBoard - .cardsDueInBetween(fetchInfo.start, fetchInfo.end) + .cardsDueInBetween(start.toDate(), end.toDate()) .forEach(function (card) { pushEvent( card, @@ -1034,36 +1325,36 @@ Template.calendarView.helpers({ }); callback(events); }, - eventResize(info) { + eventResize(event, delta, revertFunc) { let isOk = false; - const card = ReactiveCache.getCard(info.event.id); + const card = ReactiveCache.getCard(event.id); if (card) { - card.setEnd(info.event.end); + card.setEnd(event.end.toDate()); isOk = true; } if (!isOk) { - info.revert(); + revertFunc(); } }, - eventDrop(info) { + eventDrop(event, delta, revertFunc) { let isOk = false; - const card = ReactiveCache.getCard(info.event.id); + const card = ReactiveCache.getCard(event.id); if (card) { // TODO: add a flag for allDay events - if (!info.event.allDay) { + if (!event.allDay) { // https://github.com/wekan/wekan/issues/2917#issuecomment-1236753962 - //card.setStart(info.event.start); - //card.setEnd(info.event.end); - card.setDue(info.event.start); + //card.setStart(event.start.toDate()); + //card.setEnd(event.end.toDate()); + card.setDue(event.start.toDate()); isOk = true; } } if (!isOk) { - info.revert(); + revertFunc(); } }, - select: function (selectionInfo) { + select: function (startDate) { const currentBoard = Utils.getCurrentBoard(); const currentUser = ReactiveCache.getCurrentUser(); const modalElement = document.createElement('div'); @@ -1075,7 +1366,7 @@ Template.calendarView.helpers({
`; - const createCardButton = modalElement.querySelector( - '#create-card-button', - ); + const createCardButton = modalElement.querySelector('#create-card-button'); createCardButton.addEventListener('click', function () { const myTitle = modalElement.querySelector('#card-title-input').value; if (myTitle) { const firstList = currentBoard.draggableLists()[0]; const firstSwimlane = currentBoard.swimlanes()[0]; - Meteor.call( - 'createCardWithDueDate', - currentBoard._id, - firstList._id, - myTitle, - selectionInfo.start, - firstSwimlane._id, - function (error, result) { - if (error) { - if (process.env.DEBUG === 'true') { - console.log(error); - } - } else { - if (process.env.DEBUG === 'true') { - console.log('Card Created', result); - } + Meteor.call('createCardWithDueDate', currentBoard._id, firstList._id, myTitle, startDate.toDate(), firstSwimlane._id, function(error, result) { + if (error) { + if (process.env.DEBUG === 'true') { + console.log(error); } - }, - ); + } else { + if (process.env.DEBUG === 'true') { + console.log("Card Created", result); + } + } + }); closeModal(); } }); document.body.appendChild(modalElement); - const openModal = function () { + const openModal = function() { modalElement.style.display = 'flex'; // Set focus to the input field for better keyboard accessibility const input = modalElement.querySelector('#card-title-input'); if (input) input.focus(); }; - const closeModal = function () { + const closeModal = function() { modalElement.style.display = 'none'; }; - const closeButton = modalElement.querySelector( - '[data-dismiss="modal"]', - ); + const closeButton = modalElement.querySelector('[data-dismiss="modal"]'); closeButton.addEventListener('click', closeModal); openModal(); - }, + } }; }, isViewCalendar() { @@ -1144,9 +1423,4 @@ Template.calendarView.helpers({ return window.localStorage.getItem('boardView') === 'board-view-cal'; } }, -}); - -/** - * Gantt View Component - * Displays cards as a Gantt chart with start/due dates - */ +}).register('calendarView'); diff --git a/client/components/boards/boardColors.css b/client/components/boards/boardColors.css index 641f85ad7..146991d27 100644 --- a/client/components/boards/boardColors.css +++ b/client/components/boards/boardColors.css @@ -49,12 +49,6 @@ THEME - NEPHRITIS border-bottom: 2px solid #27ae60; border-right: 2px solid #27ae60; } -.board-color-nephritis .checklist-progress-bar { - background-color: #d4f1dd !important; -} -.board-color-nephritis .checklist-progress-bar .checklist-progress { - background-color: #27ae60 !important; -} .board-color-nephritis .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #e7faef; } @@ -156,12 +150,6 @@ THEME - Pomegranate border-bottom: 2px solid #c0392b; border-right: 2px solid #c0392b; } -.board-color-pomegranate .checklist-progress-bar { - background-color: #f5d5d2 !important; -} -.board-color-pomegranate .checklist-progress-bar .checklist-progress { - background-color: #c0392b !important; -} .board-color-pomegranate .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #faeae9; } @@ -263,12 +251,6 @@ THEME - Belize border-bottom: 2px solid #2980b9; border-right: 2px solid #2980b9; } -.board-color-belize .checklist-progress-bar { - background-color: #d1e7f5 !important; -} -.board-color-belize .checklist-progress-bar .checklist-progress { - background-color: #2980b9 !important; -} .board-color-belize .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #e8f3fa; } @@ -370,12 +352,6 @@ THEME - Wisteria border-bottom: 2px solid #8e44ad; border-right: 2px solid #8e44ad; } -.board-color-wisteria .checklist-progress-bar { - background-color: #e8d9f0 !important; -} -.board-color-wisteria .checklist-progress-bar .checklist-progress { - background-color: #8e44ad !important; -} .board-color-wisteria .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #f4ecf7; } @@ -477,12 +453,6 @@ THEME - Midnight border-bottom: 2px solid #2c3e50; border-right: 2px solid #2c3e50; } -.board-color-midnight .checklist-progress-bar { - background-color: #d2dae2 !important; -} -.board-color-midnight .checklist-progress-bar .checklist-progress { - background-color: #2c3e50 !important; -} .board-color-midnight .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #e6ecf1; } @@ -584,12 +554,6 @@ THEME - Pumpkin border-bottom: 2px solid #e67e22; border-right: 2px solid #e67e22; } -.board-color-pumpkin .checklist-progress-bar { - background-color: #f9e5d1 !important; -} -.board-color-pumpkin .checklist-progress-bar .checklist-progress { - background-color: #e67e22 !important; -} .board-color-pumpkin .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #fdf2e9; } @@ -691,12 +655,6 @@ THEME - Moderate Pink border-bottom: 2px solid #cd5a91; border-right: 2px solid #cd5a91; } -.board-color-moderatepink .checklist-progress-bar { - background-color: #f4dde8 !important; -} -.board-color-moderatepink .checklist-progress-bar .checklist-progress { - background-color: #cd5a91 !important; -} .board-color-moderatepink .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #faeef4; } @@ -798,12 +756,6 @@ THEME - Strong Cyan border-bottom: 2px solid #00aecc; border-right: 2px solid #00aecc; } -.board-color-strongcyan .checklist-progress-bar { - background-color: #ccf2f9 !important; -} -.board-color-strongcyan .checklist-progress-bar .checklist-progress { - background-color: #00aecc !important; -} .board-color-strongcyan .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #e0fbff; } @@ -905,12 +857,6 @@ THEME - Lime Green border-bottom: 2px solid #4bbf6b; border-right: 2px solid #4bbf6b; } -.board-color-limegreen .checklist-progress-bar { - background-color: #daf4de !important; -} -.board-color-limegreen .checklist-progress-bar .checklist-progress { - background-color: #4bbf6b !important; -} .board-color-limegreen .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #edf9f0; } @@ -1013,12 +959,6 @@ THEME - Dark border-bottom: 2px solid #2c3e51; border-right: 2px solid #2c3e51; } -.board-color-dark .checklist-progress-bar { - background-color: #d2dae2 !important; -} -.board-color-dark .checklist-progress-bar .checklist-progress { - background-color: #2c3e51 !important; -} .board-color-dark .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #e6ecf1; } @@ -1222,12 +1162,6 @@ THEME - Relax border-bottom: 2px solid #27ae61; border-right: 2px solid #27ae61; } -.board-color-relax .checklist-progress-bar { - background-color: #d4f1dd !important; -} -.board-color-relax .checklist-progress-bar .checklist-progress { - background-color: #27ae61 !important; -} .board-color-relax .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #e7faef; } @@ -1358,12 +1292,6 @@ THEME - Corteza border-bottom: 2px solid #568ba2; border-right: 2px solid #568ba2; } -.board-color-corteza .checklist-progress-bar { - background-color: #dce6ec !important; -} -.board-color-corteza .checklist-progress-bar .checklist-progress { - background-color: #568ba2 !important; -} .board-color-corteza .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #eef3f6; } @@ -1469,12 +1397,6 @@ THEME - Clear Blue border-bottom: 2px solid #499bea; border-right: 2px solid #499bea; } -.board-color-clearblue .checklist-progress-bar { - background-color: #daeefb !important; -} -.board-color-clearblue .checklist-progress-bar .checklist-progress { - background-color: #499bea !important; -} .board-color-clearblue .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #e0fbff; } @@ -1503,7 +1425,7 @@ THEME - Clear Blue } .board-color-clearblue .list { background: rgba(255,255,255,0.35); - margin: 10px 0; + margin: 10px; border: 0; border-radius: 14px; } @@ -1738,12 +1660,6 @@ THEME - Natural border-bottom: 2px solid #596557; border-right: 2px solid #596557; } -.board-color-natural .checklist-progress-bar { - background-color: #dee0dd !important; -} -.board-color-natural .checklist-progress-bar .checklist-progress { - background-color: #596557 !important; -} .board-color-natural .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #eef0ee; } @@ -1854,12 +1770,6 @@ THEME - Modern border-bottom: 2px solid #2a80b8; border-right: 2px solid #2a80b8; } -.board-color-modern .checklist-progress-bar { - background-color: #d1e7f5 !important; -} -.board-color-modern .checklist-progress-bar .checklist-progress { - background-color: #2a80b8 !important; -} .board-color-modern .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #e8f3fa; } @@ -2152,12 +2062,6 @@ THEME - Modern Dark border-bottom: 2px solid #2a2a2a; border-right: 2px solid #2a2a2a; } -.board-color-moderndark .checklist-progress-bar { - background-color: #d1d1d1 !important; -} -.board-color-moderndark .checklist-progress-bar .checklist-progress { - background-color: #2a2a2a !important; -} .board-color-moderndark .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #eaeaea; } @@ -2643,12 +2547,6 @@ THEME - Exodark border-bottom: 2px solid #dbdbdb!important;/*Fix contrast of checkbox*/ border-right: 2px solid #dbdbdb!important; } -.board-color-exodark .checklist-progress-bar { - background-color: #cccccc !important; -} -.board-color-exodark .checklist-progress-bar .checklist-progress { - background-color: #222 !important; -} .board-color-exodark .is-multiselection-active .multi-selection-checkbox.is-checked + .minicard { background: #e9e9e9; } @@ -2690,7 +2588,7 @@ THEME - Exodark background: #222; } .board-color-exodark .list { - margin: 10px 0; + margin: 10px; color: #fff; border-radius: 15px; background-color: #1c1c1c; @@ -3242,12 +3140,6 @@ THEME - Clean Dark margin-left: 3px; margin-top: 3px; } -.board-color-cleandark .checklist-progress-bar { - background-color: #6b6b78 !important; -} -.board-color-cleandark .checklist-progress-bar .checklist-progress { - background-color: #23232B !important; -} .board-color-cleandark .allBoards { white-space: nowrap; @@ -4000,13 +3892,6 @@ THEME - Clean Light margin-left: 3px; margin-top: 3px; } -.board-color-cleanlight .checklist-progress-bar { - background-color: #f5f5f5 !important; -} -.board-color-cleanlight .checklist-progress-bar .checklist-progress { - background-color: #c0c0c0 !important; - color: #010101 !important; -} .board-color-cleanlight .allBoards { white-space: nowrap; diff --git a/client/components/boards/boardHeader.jade b/client/components/boards/boardHeader.jade index 2172e70a3..013cb3619 100644 --- a/client/components/boards/boardHeader.jade +++ b/client/components/boards/boardHeader.jade @@ -14,39 +14,41 @@ template(name="boardHeaderBar") with currentBoard if currentUser.isBoardAdmin a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title) - i.fa.fa-pencil-square-o + | ✏️ + + a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}" + title="{{#if isStarred}}{{_ 'star-board-short-unstar'}}{{else}}{{_ 'star-board-short-star'}}{{/if}}" aria-label="{{#if isStarred}}{{_ 'star-board-short-unstar'}}{{else}}{{_ 'star-board-short-star'}}{{/if}}") + | {{#if isStarred}}⭐{{else}}☆{{/if}} + if showStarCounter + span + = currentBoard.stars a.board-header-btn( class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}" title="{{_ currentBoard.permission}}") - i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}") + | {{#if currentBoard.isPublic}}🌐{{else}}🔒{{/if}} span {{_ currentBoard.permission}} a.board-header-btn.js-watch-board( title="{{_ watchLevel }}") if $eq watchLevel "watching" - i.fa.fa-eye + | 👁️ if $eq watchLevel "tracking" - i.fa.fa-bell + | 🔔 if $eq watchLevel "muted" - i.fa.fa-bell-slash + | 🔕 span {{_ watchLevel}} - a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}" - title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}") - i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}") - if showStarCounter - span.board-star-counter {{currentBoard.stars}} a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}") - i.fa.fa-sort + | {{sortCardsIcon}} span {{#if isSortActive }}{{_ 'sort-is-on'}}{{else}}{{_ 'sort-cards'}}{{/if}} if isSortActive a.board-header-btn-close.js-sort-reset(title="{{_ 'remove-sort'}}") - i.fa.fa-times-thin + | ❌ else a.board-header-btn.js-log-in( title="{{_ 'log-in'}}") - i.fa.fa-sign-in + | 🚪 span {{_ 'log-in'}} .board-header-btns.center @@ -57,114 +59,99 @@ template(name="boardHeaderBar") if currentUser with currentBoard a.board-header-btn(class="{{#if currentUser.isBoardAdmin}}js-edit-board-title{{else}}is-disabled{{/if}}" title="{{_ 'edit'}}" value=title) - i.fa.fa-pencil-square-o + | ✏️ + + a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}" + title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}") + | {{#if isStarred}}⭐{{else}}☆{{/if}} a.board-header-btn( class="{{#if currentUser.isBoardAdmin}}js-change-visibility{{else}}is-disabled{{/if}}" title="{{_ currentBoard.permission}}") - i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}") + | {{#if currentBoard.isPublic}}🌐{{else}}🔒{{/if}} a.board-header-btn.js-watch-board( title="{{_ watchLevel }}") if $eq watchLevel "watching" - i.fa.fa-eye + | 👁️ if $eq watchLevel "tracking" - i.fa.fa-bell + | 🔔 if $eq watchLevel "muted" - i.fa.fa-bell-slash - a.board-header-btn.js-star-board(class="{{#if isStarred}}is-active{{/if}}" - title="{{#if isStarred}}{{_ 'click-to-unstar'}}{{else}}{{_ 'click-to-star'}}{{/if}} {{_ 'starred-boards-description'}}") - i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}") + | 🔕 a.board-header-btn(title="{{_ 'sort-cards'}}" class="{{#if isSortActive }}emphasis{{else}} js-sort-cards {{/if}}") - i.fa.fa-sort - span {{#if isSortActive }}{{_ 'sort-is-on'}}{{else}}{{_ 'sort-cards'}}{{/if}} + | {{sortCardsIcon}} if isSortActive a.board-header-btn-close.js-sort-reset(title="{{_ 'remove-sort'}}") - i.fa.fa-times-thin + | ❌ else a.board-header-btn.js-log-in( title="{{_ 'log-in'}}") - i.fa.fa-sign-in + | 🚪 if isSandstorm if currentUser a.board-header-btn.js-open-archived-board - i.fa.fa-archive + | 📦 //if showSort - // - a.board-header-btn.js-open-sort-view(title="{{_ 'sort-desc'}}") - // - i.fa(class="{{directionClass}}") - // - span {{_ 'sort'}}{{_ listSortShortDesc}} + // a.board-header-btn.js-open-sort-view(title="{{_ 'sort-desc'}}") + // i.fa(class="{{directionClass}}") + // span {{_ 'sort'}}{{_ listSortShortDesc}} a.board-header-btn.js-open-filter-view( title="{{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}}" - class="{{#if Filter.isActive}}js-filter-active{{/if}}") - i.fa.fa-filter - span {{#if Filter.isActive}}{{_ 'filter-on-desc'}}{{else}}{{_ 'filter'}}{{/if}} + class="{{#if Filter.isActive}}emphasis{{/if}}") + | 🔽 if Filter.isActive a.board-header-btn-close.js-filter-reset(title="{{_ 'filter-clear'}}") - i.fa.fa-times-thin + | ❌ a.board-header-btn.js-open-search-view(title="{{_ 'search'}}") - i.fa.fa-search - span {{_ 'search'}} + | 🔍 unless currentBoard.isTemplatesBoard - a.board-header-btn.js-toggle-board-view - i.fa.fa-caret-down + a.board-header-btn.js-toggle-board-view( + title="{{_ 'board-view'}}") + | ▼ if $eq boardView 'board-view-swimlanes' - i.fa.fa-th-large + | 🏊 if $eq boardView 'board-view-lists' - i.fa.fa-trello + | 📋 if $eq boardView 'board-view-cal' - i.fa.fa-calendar - if $eq boardView 'board-view-gantt' - i.fa.fa-bar-chart - span - if $eq boardView 'board-view-swimlanes' - | {{_ 'swimlanes'}} - if $eq boardView 'board-view-lists' - | {{_ 'lists'}} - if $eq boardView 'board-view-cal' - | {{_ 'calendar'}} - if $eq boardView 'board-view-gantt' - | {{_ 'gantt'}} + | 📅 + if canModifyBoard a.board-header-btn.js-multiselection-activate( title="{{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}" class="{{#if MultiSelection.isActive}}emphasis{{/if}}") - i.fa.fa-check-square-o - span {{#if MultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}} - if MultiSelection.isActive - a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}") - i.fa.fa-times-thin + | ☑️ + if MultiSelection.isActive + a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}") + | ❌ .separator a.board-header-btn.js-toggle-sidebar(title="{{_ 'sidebar-open'}} {{_ 'or'}} {{_ 'sidebar-close'}}") - i.fa.fa-bars + | ☰ template(name="boardVisibilityList") ul.pop-over-list li with "private" a.js-select-visibility - i.fa.fa-lock + | 🔒 | {{_ 'private'}} if visibilityCheck - i.fa.fa-check + | ✅ span.sub-name {{_ 'private-desc'}} if notAllowPrivateVisibilityOnly li with "public" a.js-select-visibility - i.fa.fa-globe + | 🌐 | {{_ 'public'}} if visibilityCheck - i.fa.fa-check + | ✅ span.sub-name {{_ 'public-desc'}} template(name="boardChangeVisibilityPopup") @@ -175,26 +162,26 @@ template(name="boardChangeWatchPopup") li with "watching" a.js-select-watch - i.fa.fa-eye + | 👁️ | {{_ 'watching'}} if watchCheck - i.fa.fa-check + | ✅ span.sub-name {{_ 'watching-info'}} li with "tracking" a.js-select-watch - i.fa.fa-bell + | 🔔 | {{_ 'tracking'}} if watchCheck - i.fa.fa-check + | ✅ span.sub-name {{_ 'tracking-info'}} li with "muted" a.js-select-watch - i.fa.fa-bell-slash + | 🔕 | {{_ 'muted'}} if watchCheck - i.fa.fa-check + | ✅ span.sub-name {{_ 'muted-info'}} template(name="boardChangeViewPopup") @@ -202,47 +189,40 @@ template(name="boardChangeViewPopup") li with "board-view-swimlanes" a.js-open-swimlanes-view - i.fa.fa-th-large - | {{_ 'swimlanes'}} + | 🏊 + | {{_ 'board-view-swimlanes'}} if $eq Utils.boardView "board-view-swimlanes" - i.fa.fa-check + | ✅ li with "board-view-lists" a.js-open-lists-view - i.fa.fa-trello + | 📋 | {{_ 'board-view-lists'}} if $eq Utils.boardView "board-view-lists" - i.fa.fa-check + | ✅ li with "board-view-cal" a.js-open-cal-view - i.fa.fa-calendar + | 📅 | {{_ 'board-view-cal'}} if $eq Utils.boardView "board-view-cal" - i.fa.fa-check - li - with "board-view-gantt" - a.js-open-gantt-view - i.fa.fa-bar-chart - | {{_ 'board-view-gantt'}} - if $eq Utils.boardView "board-view-gantt" - i.fa.fa-check + | ✅ template(name="createBoard") form label | {{_ 'title'}} input.js-new-board-title(type="text" placeholder="{{_ 'bucket-example'}}" autofocus required) - if visibilityMenuIsOpen + if visibilityMenuIsOpen.get +boardVisibilityList else p.quiet - if $eq visibility 'public' - span.fa.fa-globe.colorful + if $eq visibility.get 'public' + span 🌐 = " " | {{{_ 'board-public-info'}}} else - span.fa.fa-lock.colorful + span 🔒 = " " | {{{_ 'board-private-info'}}} a.js-change-visibility {{_ 'change'}}. @@ -262,75 +242,16 @@ template(name="createBoardPopup") label | {{_ 'title'}} input.js-new-board-title(type="text" placeholder="{{_ 'bucket-example'}}" autofocus required) - if visibilityMenuIsOpen + if visibilityMenuIsOpen.get +boardVisibilityList else p.quiet - if $eq visibility 'public' - span.fa.fa-globe.colorful + if $eq visibility.get 'public' + span 🌐 = " " | {{{_ 'board-public-info'}}} else - span.fa.fa-lock.colorful - = " " - | {{{_ 'board-private-info'}}} - a.js-change-visibility {{_ 'change'}}. - a.flex.js-toggle-add-template-container - .materialCheckBox#add-template-container - span {{_ 'add-template-container'}} - input.primary.wide(type="submit" value="{{_ 'create'}}") - span.quiet - | {{_ 'or'}} - a.js-import-board {{_ 'import'}} - span.quiet - | / - a.js-board-template {{_ 'template'}} - -template(name="headerBarCreateBoardPopup") - form - label - | {{_ 'title'}} - input.js-new-board-title(type="text" placeholder="{{_ 'bucket-example'}}" autofocus required) - if visibilityMenuIsOpen - +boardVisibilityList - else - p.quiet - if $eq visibility 'public' - span.fa.fa-globe.colorful - = " " - | {{{_ 'board-public-info'}}} - else - span.fa.fa-lock.colorful - = " " - | {{{_ 'board-private-info'}}} - a.js-change-visibility {{_ 'change'}}. - a.flex.js-toggle-add-template-container - .materialCheckBox#add-template-container - span {{_ 'add-template-container'}} - input.primary.wide(type="submit" value="{{_ 'create'}}") - span.quiet - | {{_ 'or'}} - a.js-import-board {{_ 'import'}} - span.quiet - | / - a.js-board-template {{_ 'template'}} - -// New popup for Template Container creation; shares the same form content -template(name="createTemplateContainerPopup") - form - label - | {{_ 'title'}} - input.js-new-board-title(type="text" placeholder="{{_ 'bucket-example'}}" autofocus required) - if visibilityMenuIsOpen - +boardVisibilityList - else - p.quiet - if $eq visibility 'public' - span.fa.fa-globe.colorful - = " " - | {{{_ 'board-public-info'}}} - else - span.fa.fa-lock.colorful + span 🔒 = " " | {{{_ 'board-private-info'}}} a.js-change-visibility {{_ 'change'}}. @@ -346,30 +267,19 @@ template(name="createTemplateContainerPopup") a.js-board-template {{_ 'template'}} //template(name="listsortPopup") -// - h2 -// - | {{_ 'list-sort-by'}} -// - hr -// - ul.pop-over-list -// - each value in allowedSortValues -// - li -// - a.js-sort-by(name="{{value.name}}") -// - if $eq sortby value.name -// - | {{#if $eq Direction "fa-arrow-up"}}⬆️{{else}}⬇️{{/if}} -// - | {{_ value.label }}{{_ value.shortLabel}} -// - if $eq sortby value.name -// - i.fa.fa-check +// h2 +// | {{_ 'list-sort-by'}} +// hr +// ul.pop-over-list +// each value in allowedSortValues +// li +// a.js-sort-by(name="{{value.name}}") +// if $eq sortby value.name +// | {{#if $eq Direction "fa-arrow-up"}}⬆️{{else}}⬇️{{/if}} +// | {{_ value.label }}{{_ value.shortLabel}} +// if $eq sortby value.name +// | ✅ + template(name="boardChangeTitlePopup") form label @@ -389,21 +299,21 @@ template(name="cardsSortPopup") ul.pop-over-list li a.js-sort-due - i.fa.fa-calendar + | 📅 | {{_ 'due-date'}} hr li a.js-sort-title - i.fa.fa-sort-alpha-asc + | 🔤 | {{_ 'title-alphabetically'}} hr li a.js-sort-created-desc - i.fa.fa-arrow-down + | ⬇️ | {{_ 'created-at-newest-first'}} hr li a.js-sort-created-asc - i.fa.fa-arrow-up + | ⬆️ | {{_ 'created-at-oldest-first'}} diff --git a/client/components/boards/boardHeader.js b/client/components/boards/boardHeader.js index 9ceba3147..b95a45395 100644 --- a/client/components/boards/boardHeader.js +++ b/client/components/boards/boardHeader.js @@ -1,6 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import dragscroll from '@wekanteam/dragscroll'; /* @@ -10,7 +9,7 @@ const UPCLS = 'fa-sort-up'; const sortCardsBy = new ReactiveVar(''); Template.boardChangeTitlePopup.events({ - async submit(event, templateInstance) { + submit(event, templateInstance) { const newTitle = templateInstance .$('.js-board-name') .val() @@ -20,23 +19,22 @@ Template.boardChangeTitlePopup.events({ .val() .trim(); if (newTitle) { - const board = Utils.getCurrentBoard(); - if (board) { - await board.rename(newTitle); - await board.setDescription(newDesc); - } + this.rename(newTitle); + this.setDescription(newDesc); Popup.back(); } event.preventDefault(); }, }); -Template.boardHeaderBar.helpers({ +BlazeComponent.extendComponent({ watchLevel() { const currentBoard = Utils.getCurrentBoard(); return currentBoard && currentBoard.getWatchLevel(Meteor.userId()); }, + + isStarred() { const boardId = Session.get('currentBoard'); const user = ReactiveCache.getCurrentUser(); @@ -48,7 +46,127 @@ Template.boardHeaderBar.helpers({ const currentBoard = Utils.getCurrentBoard(); return currentBoard && currentBoard.stars >= 2; }, + /* + showSort() { + return ReactiveCache.getCurrentUser().hasSortBy(); + }, + directionClass() { + return this.currentDirection() === -1 ? DOWNCLS : UPCLS; + }, + changeDirection() { + const direction = 0 - this.currentDirection() === -1 ? '-' : ''; + Meteor.call('setListSortBy', direction + this.currentListSortBy()); + }, + currentDirection() { + return ReactiveCache.getCurrentUser().getListSortByDirection(); + }, + currentListSortBy() { + return ReactiveCache.getCurrentUser().getListSortBy(); + }, + listSortShortDesc() { + return `list-label-short-${this.currentListSortBy()}`; + }, + */ + events() { + return [ + { + 'click .js-edit-board-title': Popup.open('boardChangeTitle'), + 'click .js-star-board'() { + ReactiveCache.getCurrentUser().toggleBoardStar(Session.get('currentBoard')); + }, + 'click .js-open-board-menu': Popup.open('boardMenu'), + 'click .js-change-visibility': Popup.open('boardChangeVisibility'), + 'click .js-watch-board': Popup.open('boardChangeWatch'), + 'click .js-open-archived-board'() { + Modal.open('archivedBoards'); + }, + 'click .js-toggle-board-view': Popup.open('boardChangeView'), + 'click .js-toggle-sidebar'() { + if (process.env.DEBUG === 'true') { + console.log('Hamburger menu clicked'); + } + // Use the same approach as keyboard shortcuts + if (typeof Sidebar !== 'undefined' && Sidebar && typeof Sidebar.toggle === 'function') { + if (process.env.DEBUG === 'true') { + console.log('Using Sidebar.toggle()'); + } + Sidebar.toggle(); + } else { + if (process.env.DEBUG === 'true') { + console.warn('Sidebar not available, trying alternative approach'); + } + // Try to trigger the sidebar through the global Blaze helper + if (typeof Blaze !== 'undefined' && Blaze._globalHelpers && Blaze._globalHelpers.Sidebar) { + const sidebar = Blaze._globalHelpers.Sidebar(); + if (sidebar && typeof sidebar.toggle === 'function') { + if (process.env.DEBUG === 'true') { + console.log('Using Blaze helper Sidebar.toggle()'); + } + sidebar.toggle(); + } + } + } + }, + 'click .js-open-filter-view'() { + if (Sidebar) { + Sidebar.setView('filter'); + } else { + console.warn('Sidebar not available for setView'); + } + }, + 'click .js-sort-cards': Popup.open('cardsSort'), + /* + 'click .js-open-sort-view'(evt) { + const target = evt.target; + if (target.tagName === 'I') { + // click on the text, popup choices + this.changeDirection(); + } else { + // change the sort order + Popup.open('listsort')(evt); + } + }, + */ + 'click .js-filter-reset'(event) { + event.stopPropagation(); + if (Sidebar) { + Sidebar.setView(); + } else { + console.warn('Sidebar not available for setView'); + } + Filter.reset(); + }, + 'click .js-sort-reset'() { + Session.set('sortBy', ''); + }, + 'click .js-open-search-view'() { + if (Sidebar) { + Sidebar.setView('search'); + } else { + console.warn('Sidebar not available for setView'); + } + }, + 'click .js-multiselection-activate'() { + const currentCard = Utils.getCurrentCardId(); + MultiSelection.activate(); + if (currentCard) { + MultiSelection.add(currentCard); + } + }, + 'click .js-multiselection-reset'(event) { + event.stopPropagation(); + MultiSelection.disable(); + }, + 'click .js-log-in'() { + FlowRouter.go('atSignIn'); + }, + }, + ]; + }, +}).register('boardHeaderBar'); + +Template.boardHeaderBar.helpers({ boardView() { return Utils.boardView(); }, @@ -60,7 +178,7 @@ Template.boardHeaderBar.helpers({ if (!sortBy) { return '🃏'; // Card icon when nothing is selected } - + // Determine which sort option is active based on sortBy object if (sortBy.dueAt) { return '📅'; // Due date icon @@ -69,107 +187,11 @@ Template.boardHeaderBar.helpers({ } else if (sortBy.createdAt) { return sortBy.createdAt === 1 ? '⬆️' : '⬇️'; // Up/down arrow based on direction } - + return '🃏'; // Default card icon }, }); -Template.boardHeaderBar.events({ - 'click .js-edit-board-title': Popup.open('boardChangeTitle'), - 'click .js-star-board'() { - const boardId = Session.get('currentBoard'); - if (boardId) { - Meteor.call('toggleBoardStar', boardId); - } - }, - 'click .js-open-board-menu': Popup.open('boardMenu'), - 'click .js-change-visibility': Popup.open('boardChangeVisibility'), - 'click .js-watch-board': Popup.open('boardChangeWatch'), - 'click .js-open-archived-board'() { - Modal.open('archivedBoards'); - }, - 'click .js-toggle-board-view': Popup.open('boardChangeView'), - 'click .js-toggle-sidebar'() { - if (process.env.DEBUG === 'true') { - console.log('Hamburger menu clicked'); - } - // Use the same approach as keyboard shortcuts - if (typeof Sidebar !== 'undefined' && Sidebar && typeof Sidebar.toggle === 'function') { - if (process.env.DEBUG === 'true') { - console.log('Using Sidebar.toggle()'); - } - Sidebar.toggle(); - } else { - if (process.env.DEBUG === 'true') { - console.warn('Sidebar not available, trying alternative approach'); - } - // Try to trigger the sidebar through the global Blaze helper - if (typeof Blaze !== 'undefined' && Blaze._globalHelpers && Blaze._globalHelpers.Sidebar) { - const sidebar = Blaze._globalHelpers.Sidebar(); - if (sidebar && typeof sidebar.toggle === 'function') { - if (process.env.DEBUG === 'true') { - console.log('Using Blaze helper Sidebar.toggle()'); - } - sidebar.toggle(); - } - } - } - }, - 'click .js-open-filter-view'() { - if (Sidebar) { - Sidebar.setView('filter'); - } else { - console.warn('Sidebar not available for setView'); - } - }, - 'click .js-sort-cards': Popup.open('cardsSort'), - /* - 'click .js-open-sort-view'(evt) { - const target = evt.target; - if (target.tagName === 'I') { - // click on the text, popup choices - this.changeDirection(); - } else { - // change the sort order - Popup.open('listsort')(evt); - } - }, - */ - 'click .js-filter-reset'(event) { - event.stopPropagation(); - if (Sidebar) { - Sidebar.setView(); - } else { - console.warn('Sidebar not available for setView'); - } - Filter.reset(); - }, - 'click .js-sort-reset'() { - Session.set('sortBy', ''); - }, - 'click .js-open-search-view'() { - if (Sidebar) { - Sidebar.setView('search'); - } else { - console.warn('Sidebar not available for setView'); - } - }, - 'click .js-multiselection-activate'() { - const currentCard = Utils.getCurrentCardId(); - MultiSelection.activate(); - if (currentCard) { - MultiSelection.add(currentCard); - } - }, - 'click .js-multiselection-reset'(event) { - event.stopPropagation(); - MultiSelection.disable(); - }, - 'click .js-log-in'() { - FlowRouter.go('atSignIn'); - }, -}); - Template.boardChangeViewPopup.events({ 'click .js-open-lists-view'() { Utils.setBoardView('board-view-lists'); @@ -183,263 +205,196 @@ Template.boardChangeViewPopup.events({ Utils.setBoardView('board-view-cal'); Popup.back(); }, - 'click .js-open-gantt-view'() { - Utils.setBoardView('board-view-gantt'); - Popup.back(); +}); + +const CreateBoard = BlazeComponent.extendComponent({ + template() { + return 'createBoard'; }, -}); -// Shared setup for all create board popups -function setupCreateBoardState(tpl) { - tpl.visibilityMenuIsOpen = new ReactiveVar(false); - tpl.visibility = new ReactiveVar('private'); - tpl.boardId = new ReactiveVar(''); - Meteor.subscribe('tableVisibilityModeSettings'); -} - -function createBoardHelpers() { - return { - visibilityMenuIsOpen() { - return Template.instance().visibilityMenuIsOpen.get(); - }, - visibility() { - return Template.instance().visibility.get(); - }, - notAllowPrivateVisibilityOnly() { - return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue; - }, - visibilityCheck() { - return Template.currentData() === Template.instance().visibility.get(); - }, - }; -} - -function createBoardSubmit(tpl, event) { - event.preventDefault(); - const title = tpl.find('.js-new-board-title').value; - - const addTemplateContainer = $('#add-template-container.is-checked').length > 0; - if (addTemplateContainer) { - //const templateContainerId = Meteor.call('setCreateTemplateContainer'); - //Utils.goBoardId(templateContainerId); - //alert('niinku template ' + Meteor.call('setCreateTemplateContainer')); - - tpl.boardId.set( - Boards.insert({ - // title: TAPi18n.__('templates'), - title: title, - permission: 'private', - type: 'template-container', - migrationVersion: 1, // Latest version - no migration needed - }), - ); - - // Insert the card templates swimlane - Swimlanes.insert({ - // title: TAPi18n.__('card-templates-swimlane'), - title: 'Card Templates', - boardId: tpl.boardId.get(), - sort: 1, - type: 'template-container', - }), - - // Insert the list templates swimlane - Swimlanes.insert( - { - // title: TAPi18n.__('list-templates-swimlane'), - title: 'List Templates', - boardId: tpl.boardId.get(), - sort: 2, - type: 'template-container', - }, - ); - - // Insert the board templates swimlane - Swimlanes.insert( - { - //title: TAPi18n.__('board-templates-swimlane'), - title: 'Board Templates', - boardId: tpl.boardId.get(), - sort: 3, - type: 'template-container', - }, - ); - - // Assign to space if one was selected - const spaceId = Session.get('createBoardInWorkspace'); - if (spaceId) { - Meteor.call('assignBoardToWorkspace', tpl.boardId.get(), spaceId, (err) => { - if (err) console.error('Error assigning board to space:', err); - }); - Session.set('createBoardInWorkspace', null); // Clear after use - } - - Utils.goBoardId(tpl.boardId.get()); - - } else { - const visibility = tpl.visibility.get(); - - tpl.boardId.set( - Boards.insert({ - title, - permission: visibility, - migrationVersion: 1, // Latest version - no migration needed - }), - ); - - Swimlanes.insert({ - title: 'Default', - boardId: tpl.boardId.get(), - }); - - // Assign to space if one was selected - const spaceId = Session.get('createBoardInWorkspace'); - if (spaceId) { - Meteor.call('assignBoardToWorkspace', tpl.boardId.get(), spaceId, (err) => { - if (err) console.error('Error assigning board to space:', err); - }); - Session.set('createBoardInWorkspace', null); // Clear after use - } - - Utils.goBoardId(tpl.boardId.get()); - } -} - -function createBoardEvents() { - return { - 'click .js-select-visibility'(event, tpl) { - tpl.visibility.set(Template.currentData()); - tpl.visibilityMenuIsOpen.set(false); - }, - 'click .js-change-visibility'(event, tpl) { - tpl.visibilityMenuIsOpen.set(!tpl.visibilityMenuIsOpen.get()); - }, - 'click .js-import': Popup.open('boardImportBoard'), - 'submit'(event, tpl) { - createBoardSubmit(tpl, event); - }, - 'click .js-import-board': Popup.open('chooseBoardSource'), - 'click .js-board-template': Popup.open('searchElement'), - 'click .js-toggle-add-template-container'() { - $('#add-template-container').toggleClass('is-checked'); - }, - }; -} - -// createBoard (non-popup version) -Template.createBoard.onCreated(function () { - setupCreateBoardState(this); -}); - -Template.createBoard.helpers(createBoardHelpers()); - -Template.createBoard.events(createBoardEvents()); - -// createBoardPopup -Template.createBoardPopup.onCreated(function () { - setupCreateBoardState(this); -}); - -Template.createBoardPopup.helpers(createBoardHelpers()); - -Template.createBoardPopup.events(createBoardEvents()); - -// createTemplateContainerPopup -Template.createTemplateContainerPopup.onCreated(function () { - setupCreateBoardState(this); -}); - -Template.createTemplateContainerPopup.onRendered(function () { - // Always pre-check the template container checkbox for this popup - $('#add-template-container').addClass('is-checked'); -}); - -Template.createTemplateContainerPopup.helpers(createBoardHelpers()); - -Template.createTemplateContainerPopup.events(createBoardEvents()); - -// headerBarCreateBoardPopup -Template.headerBarCreateBoardPopup.onCreated(function () { - setupCreateBoardState(this); -}); - -Template.headerBarCreateBoardPopup.helpers(createBoardHelpers()); - -Template.headerBarCreateBoardPopup.events({ - 'click .js-select-visibility'(event, tpl) { - tpl.visibility.set(Template.currentData()); - tpl.visibilityMenuIsOpen.set(false); + onCreated() { + this.visibilityMenuIsOpen = new ReactiveVar(false); + this.visibility = new ReactiveVar('private'); + this.boardId = new ReactiveVar(''); + Meteor.subscribe('tableVisibilityModeSettings'); }, - 'click .js-change-visibility'(event, tpl) { - tpl.visibilityMenuIsOpen.set(!tpl.visibilityMenuIsOpen.get()); + + notAllowPrivateVisibilityOnly(){ + return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue; }, - 'click .js-import': Popup.open('boardImportBoard'), - async submit(event, tpl) { - createBoardSubmit(tpl, event); - // Immediately star boards created with the headerbar popup. - await ReactiveCache.getCurrentUser().toggleBoardStar(tpl.boardId.get()); + + visibilityCheck() { + return this.currentData() === this.visibility.get(); }, - 'click .js-import-board': Popup.open('chooseBoardSource'), - 'click .js-board-template': Popup.open('searchElement'), - 'click .js-toggle-add-template-container'() { + + setVisibility(visibility) { + this.visibility.set(visibility); + this.visibilityMenuIsOpen.set(false); + }, + + toggleVisibilityMenu() { + this.visibilityMenuIsOpen.set(!this.visibilityMenuIsOpen.get()); + }, + + toggleAddTemplateContainer() { $('#add-template-container').toggleClass('is-checked'); }, -}); -Template.boardChangeVisibilityPopup.onCreated(function () { - Meteor.subscribe('tableVisibilityModeSettings'); -}); + onSubmit(event) { + event.preventDefault(); + const title = this.find('.js-new-board-title').value; -Template.boardChangeVisibilityPopup.helpers({ + const addTemplateContainer = $('#add-template-container.is-checked').length > 0; + if (addTemplateContainer) { + //const templateContainerId = Meteor.call('setCreateTemplateContainer'); + //Utils.goBoardId(templateContainerId); + //alert('niinku template ' + Meteor.call('setCreateTemplateContainer')); + + this.boardId.set( + Boards.insert({ + // title: TAPi18n.__('templates'), + title: title, + permission: 'private', + type: 'template-container', + migrationVersion: 1, // Latest version - no migration needed + }), + ); + + // Insert the card templates swimlane + Swimlanes.insert({ + // title: TAPi18n.__('card-templates-swimlane'), + title: 'Card Templates', + boardId: this.boardId.get(), + sort: 1, + type: 'template-container', + }), + + // Insert the list templates swimlane + Swimlanes.insert( + { + // title: TAPi18n.__('list-templates-swimlane'), + title: 'List Templates', + boardId: this.boardId.get(), + sort: 2, + type: 'template-container', + }, + ); + + // Insert the board templates swimlane + Swimlanes.insert( + { + //title: TAPi18n.__('board-templates-swimlane'), + title: 'Board Templates', + boardId: this.boardId.get(), + sort: 3, + type: 'template-container', + }, + ); + + Utils.goBoardId(this.boardId.get()); + + } else { + const visibility = this.visibility.get(); + + this.boardId.set( + Boards.insert({ + title, + permission: visibility, + migrationVersion: 1, // Latest version - no migration needed + }), + ); + + Swimlanes.insert({ + title: 'Default', + boardId: this.boardId.get(), + }); + + Utils.goBoardId(this.boardId.get()); + } + }, + + events() { + return [ + { + 'click .js-select-visibility'() { + this.setVisibility(this.currentData()); + }, + 'click .js-change-visibility': this.toggleVisibilityMenu, + 'click .js-import': Popup.open('boardImportBoard'), + submit: this.onSubmit, + 'click .js-import-board': Popup.open('chooseBoardSource'), + 'click .js-board-template': Popup.open('searchElement'), + 'click .js-toggle-add-template-container': this.toggleAddTemplateContainer, + }, + ]; + }, +}).register('createBoardPopup'); + +(class HeaderBarCreateBoard extends CreateBoard { + onSubmit(event) { + super.onSubmit(event); + // Immediately star boards crated with the headerbar popup. + ReactiveCache.getCurrentUser().toggleBoardStar(this.boardId.get()); + } +}.register('headerBarCreateBoardPopup')); + +BlazeComponent.extendComponent({ notAllowPrivateVisibilityOnly(){ return !TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue; }, visibilityCheck() { const currentBoard = Utils.getCurrentBoard(); - return this === currentBoard.permission; + return this.currentData() === currentBoard.permission; }, -}); -Template.boardChangeVisibilityPopup.events({ - 'click .js-select-visibility'() { + selectBoardVisibility() { const currentBoard = Utils.getCurrentBoard(); - const visibility = String(this); + const visibility = this.currentData(); currentBoard.setVisibility(visibility); Popup.back(); }, -}); -Template.boardChangeWatchPopup.helpers({ + events() { + return [ + { + 'click .js-select-visibility': this.selectBoardVisibility, + }, + ]; + }, +}).register('boardChangeVisibilityPopup'); + +BlazeComponent.extendComponent({ watchLevel() { const currentBoard = Utils.getCurrentBoard(); return currentBoard.getWatchLevel(Meteor.userId()); }, watchCheck() { - const currentBoard = Utils.getCurrentBoard(); - return this === currentBoard.getWatchLevel(Meteor.userId()); + return this.currentData() === this.watchLevel(); }, -}); -Template.boardChangeWatchPopup.events({ - 'click .js-select-watch'() { - const level = String(this); - Meteor.call( - 'watch', - 'board', - Session.get('currentBoard'), - level, - (err, ret) => { - if (!err && ret) Popup.back(); + events() { + return [ + { + 'click .js-select-watch'() { + const level = this.currentData(); + Meteor.call( + 'watch', + 'board', + Session.get('currentBoard'), + level, + (err, ret) => { + if (!err && ret) Popup.back(); + }, + ); + }, }, - ); + ]; }, -}); +}).register('boardChangeWatchPopup'); /* -// BlazeComponent.extendComponent was removed - this code is unused. -// Original listsortPopup component: -// { +BlazeComponent.extendComponent({ onCreated() { //this.sortBy = new ReactiveVar(); ////this.sortDirection = new ReactiveVar(); @@ -507,40 +462,46 @@ Template.boardChangeWatchPopup.events({ }, ]; }, -// }.register('listsortPopup'); +}).register('listsortPopup'); */ -Template.cardsSortPopup.events({ - 'click .js-sort-due'() { - const sortBy = { - dueAt: 1, - }; - Session.set('sortBy', sortBy); - sortCardsBy.set(TAPi18n.__('due-date')); - Popup.back(); +BlazeComponent.extendComponent({ + events() { + return [ + { + 'click .js-sort-due'() { + const sortBy = { + dueAt: 1, + }; + Session.set('sortBy', sortBy); + sortCardsBy.set(TAPi18n.__('due-date')); + Popup.back(); + }, + 'click .js-sort-title'() { + const sortBy = { + title: 1, + }; + Session.set('sortBy', sortBy); + sortCardsBy.set(TAPi18n.__('title')); + Popup.back(); + }, + 'click .js-sort-created-asc'() { + const sortBy = { + createdAt: 1, + }; + Session.set('sortBy', sortBy); + sortCardsBy.set(TAPi18n.__('date-created-newest-first')); + Popup.back(); + }, + 'click .js-sort-created-desc'() { + const sortBy = { + createdAt: -1, + }; + Session.set('sortBy', sortBy); + sortCardsBy.set(TAPi18n.__('date-created-oldest-first')); + Popup.back(); + }, + }, + ]; }, - 'click .js-sort-title'() { - const sortBy = { - title: 1, - }; - Session.set('sortBy', sortBy); - sortCardsBy.set(TAPi18n.__('title')); - Popup.back(); - }, - 'click .js-sort-created-asc'() { - const sortBy = { - createdAt: 1, - }; - Session.set('sortBy', sortBy); - sortCardsBy.set(TAPi18n.__('date-created-newest-first')); - Popup.back(); - }, - 'click .js-sort-created-desc'() { - const sortBy = { - createdAt: -1, - }; - Session.set('sortBy', sortBy); - sortCardsBy.set(TAPi18n.__('date-created-oldest-first')); - Popup.back(); - }, -}); +}).register('cardsSortPopup'); diff --git a/client/components/boards/boardsList.css b/client/components/boards/boardsList.css index 9ba4ee1c7..f834af830 100644 --- a/client/components/boards/boardsList.css +++ b/client/components/boards/boardsList.css @@ -8,273 +8,6 @@ padding: 1vh 0; } -/* Two-column layout for All Boards */ -.boards-layout { - display: grid; - grid-template-columns: 260px 1fr; - gap: 16px; -} - -.boards-left-menu { - border-right: 1px solid #e0e0e0; - padding-right: 12px; -} - -.boards-left-menu ul.menu { - list-style: none; - padding: 0; - margin: 0 0 12px 0; -} - -.boards-left-menu .menu-item { - margin: 4px 0; -} -.boards-left-menu .menu-item a { - display: flex; - justify-content: space-between; - align-items: center; - padding: 8px 10px; - border-radius: 4px; - cursor: pointer; -} -.boards-left-menu .menu-item .menu-label { - flex: 1; -} -.boards-left-menu .menu-item .menu-count { - background: #ddd; - padding: 2px 8px; - border-radius: 12px; - font-size: 12px; - font-weight: bold; - margin-left: 8px; -} -.boards-left-menu .menu-item.active a, -.boards-left-menu .menu-item a:hover { - background: #f0f0f0; -} -.boards-left-menu .menu-item.active .menu-count { - background: #bbb; -} - -/* Drag-over state for menu items (for dropping boards on Remaining) */ -.boards-left-menu .menu-item a.drag-over { - background: #d0e8ff; - border: 2px dashed #2196F3; -} - -.workspaces-header { - display: flex; - align-items: center; - justify-content: space-between; - font-weight: bold; - margin-top: 12px; -} -.workspaces-header .js-add-space { - text-decoration: none; - font-weight: bold; - border: 1px solid #ccc; - padding: 2px 8px; - border-radius: 4px; -} - -.workspace-tree { - list-style: none; - padding-left: 10px; -} - -.workspace-node { - margin: 2px 0; - position: relative; -} - -.workspace-node-content { - display: flex; - align-items: center; - gap: 4px; - padding: 4px; - border-radius: 4px; - transition: background-color 0.2s; -} - -.workspace-node.dragging > .workspace-node-content { - opacity: 0.5; - background: #e0e0e0; -} - -.workspace-node.drag-over > .workspace-node-content { - background: #d0e8ff; - border: 2px dashed #2196F3; -} - -.workspace-drag-handle { - cursor: grab; - color: #999; - font-size: 14px; - padding: 0 4px; - user-select: none; -} - -.workspace-drag-handle:active { - cursor: grabbing; -} - -.workspace-node .js-select-space { - display: flex; - align-items: center; - gap: 6px; - padding: 4px 8px; - border-radius: 4px; - cursor: pointer; - flex: 1; - text-decoration: none; -} - -.workspace-node .workspace-icon { - font-size: 16px; - line-height: 1; -} - -.workspace-node .workspace-name { - flex: 1; -} - -.workspace-node .workspace-count { - background: #ddd; - padding: 2px 6px; - border-radius: 10px; - font-size: 11px; - font-weight: bold; - min-width: 20px; - text-align: center; -} - -.workspace-node .js-edit-space, -.workspace-node .js-add-subspace { - padding: 2px 6px; - border-radius: 3px; - cursor: pointer; - text-decoration: none; - font-size: 14px; - opacity: 0.6; - transition: opacity 0.2s; -} - -.workspace-node .js-edit-space:hover, -.workspace-node .js-add-subspace:hover { - opacity: 1; - background: #e0e0e0; -} - -.workspace-node.active > .workspace-node-content .js-select-space, -.workspace-node > .workspace-node-content:hover .js-select-space { - background: #f0f0f0; -} - -.workspace-node.active .workspace-count { - background: #bbb; -} - -.boards-right-grid { - min-height: 200px; -} - -.boards-path-header { - display: flex; - align-items: center; - justify-content: space-between; - gap: 8px; - padding: 12px 16px; - margin-bottom: 16px; - background: #f5f5f5; - border-radius: 6px; - font-size: 16px; - font-weight: 500; -} - -.boards-path-header .path-left { - display: flex; - align-items: center; - gap: 8px; - flex: 1; -} - -.boards-path-header .multiselection-hint { - background: #FFF3CD; - color: #856404; - padding: 4px 12px; - border-radius: 4px; - font-size: 13px; - font-weight: normal; - border: 1px solid #FFE69C; - animation: pulse 2s ease-in-out infinite; -} - -@keyframes pulse { - 0%, 100% { opacity: 1; } - 50% { opacity: 0.7; } -} - -.boards-path-header .path-right { - display: flex; - align-items: center; - gap: 8px; -} - -.boards-path-header .path-icon { - font-size: 18px; -} - -.boards-path-header .path-text { - color: #333; -} - -.boards-path-header .board-header-btn { - padding: 6px 12px; - background: #fff; - border: 1px solid #ddd; - border-radius: 4px; - cursor: pointer; - display: flex; - align-items: center; - gap: 6px; - font-size: 14px; - transition: all 0.2s; -} - -.boards-path-header .board-header-btn:hover { - background: #f0f0f0; - border-color: #bbb; -} - -.boards-path-header .board-header-btn.emphasis { - background: #2196F3; - color: #fff; - border-color: #2196F3; - font-weight: bold; - box-shadow: 0 2px 8px rgba(33, 150, 243, 0.5); - transform: scale(1.05); -} - -.boards-path-header .board-header-btn.emphasis:hover { - background: #1976D2; - box-shadow: 0 3px 12px rgba(33, 150, 243, 0.7); -} - -.boards-path-header .board-header-btn-close { - padding: 4px 10px; - background: #f44336; - color: #000; - border: none; - border-radius: 4px; - cursor: pointer; - font-size: 16px; - margin-left: 10px; /* Extra space between MultiSelection toggle and Remove Filter */ -} - -.boards-path-header .board-header-btn-close:hover { - background: #d32f2f; -} - .zoom-controls { display: flex; align-items: center; @@ -373,35 +106,23 @@ .board-list li.starred .is-star-active, .board-list li.starred .is-not-star-active { opacity: 1; - color: #ffd700; -} -/* Show star icon on hover even for non-starred boards */ -.board-list li:hover .is-star-active, -.board-list li:hover .is-not-star-active { - opacity: 1; } .board-list .board-list-item { overflow: hidden; - background-color: inherit; /* Inherit board color from parent li.js-board */ + background-color: #999; color: #f6f6f6; min-height: 100px; font-size: 16px; line-height: 22px; - border-radius: 0; /* No border-radius - parent .js-board has it */ + border-radius: 3px; display: block; font-weight: 700; - padding: 36px 8px 32px 8px; /* Top padding for drag handle, bottom for checkbox */ - margin: 0; /* No margin - moved to parent .js-board */ + padding: 8px; + margin: 8px; position: relative; text-decoration: none; word-wrap: break-word; } - -.board-list .board-list-item > .js-open-board { - text-decoration: none; - color: inherit; - display: block; -} .board-list .board-list-item.template-container { border: 4px solid #fff; } @@ -429,27 +150,13 @@ .board-list .js-add-board .label { font-weight: normal; line-height: 56px; - min-height: 100px; - display: flex; - align-items: center; - justify-content: center; - background-color: #999; /* Darker background for better text contrast */ - border-radius: 3px; - padding: 36px 8px 32px 8px; - color: #fff; /* White text */ } -.board-list .js-add-board .label i { - color: #fff; /* White icon */ -} -.board-list .js-add-board .label:hover { - background-color: #808080; /* Even darker on hover */ -} -.board-list .js-add-board .label:hover i { - color: #fff; /* Keep icon white on hover */ +.board-list .js-add-board :hover { + background-color: #939393; } .board-list .is-star-active, .board-list .is-not-star-active { - top: 0; + bottom: 0; font-size: 14px; height: 18px; line-height: 18px; @@ -457,6 +164,7 @@ padding: 9px 9px; position: absolute; right: 0; + top: 0; transition-duration: 0.15s; transition-property: color, font-size, background; } @@ -530,107 +238,6 @@ .board-list li:hover a .is-not-star-active { opacity: 1; } - -/* Board drag handle - always visible and positioned at top */ -.board-list .board-handle { - position: absolute; - padding: 4px 6px; - top: 4px; - left: 50%; - transform: translateX(-50%); - font-size: 14px; - color: #fff; - background: rgba(0,0,0,0.4); - border-radius: 4px; - display: flex; - align-items: center; - justify-content: center; - z-index: 10; - transition: background-color 0.2s ease; - cursor: grab; - opacity: 1; - user-select: none; -} - -.board-list .board-handle:active { - cursor: grabbing; -} - -.board-list .board-handle:hover { - background: rgba(255, 255, 0, 0.8) !important; - color: #000; -} - -/* Multiselection checkbox on board items */ -.board-list .board-list-item .multi-selection-checkbox { - position: absolute !important; - bottom: 4px !important; - left: 4px !important; - top: auto !important; - width: 24px; - height: 24px; - border: 3px solid #fff; - background: rgba(0,0,0,0.5); - border-radius: 4px; - cursor: pointer; - z-index: 11; - display: flex; - align-items: center; - justify-content: center; - transition: all 0.2s; - box-shadow: 0 2px 4px rgba(0,0,0,0.3); - transform: none !important; - margin: 0 !important; -} - -.board-list .board-list-item .multi-selection-checkbox:hover { - background: rgba(0,0,0,0.7); - transform: scale(1.15) !important; - box-shadow: 0 3px 6px rgba(0,0,0,0.5); -} - -.board-list .board-list-item .multi-selection-checkbox.is-checked { - background: #3cb500; - border-color: #3cb500; - box-shadow: 0 2px 8px rgba(60, 181, 0, 0.6); - width: 24px !important; - height: 24px !important; - top: auto !important; - left: 4px !important; - transform: none !important; - border-radius: 4px !important; -} - -.board-list .board-list-item .multi-selection-checkbox.is-checked::after { - content: '✓'; - color: #fff; - font-size: 16px; - font-weight: bold; -} - -/* Grey checkboxes when grey icons setting is enabled */ -body.grey-icons-enabled .board-list .board-list-item .multi-selection-checkbox.is-checked { - background: #7a7a7a; - border-color: #7a7a7a; - box-shadow: 0 2px 8px rgba(122, 122, 122, 0.6); -} - -body.grey-icons-enabled .board-list.is-multiselection-active .js-board.is-checked { - outline: 4px solid #7a7a7a; - box-shadow: 0 4px 12px rgba(122, 122, 122, 0.4); -} - -.board-list.is-multiselection-active .js-board.is-checked { - outline: 4px solid #3cb500; - outline-offset: -4px; - box-shadow: 0 4px 12px rgba(60, 181, 0, 0.4); -} - -/* Visual hint when multiselection is active */ -.board-list.is-multiselection-active .board-list-item { - border: 2px dashed rgba(33, 150, 243, 0.3); -} - .board-backgrounds-list .board-background-select { box-sizing: border-box; display: block; @@ -664,19 +271,8 @@ body.grey-icons-enabled .board-list.is-multiselection-active .js-board.is-checke } .board-backgrounds-list .board-background-select .background-box i.fa-check { font-size: 25px; - color: #3cb500; + color: #fff; } -/* Grey check icons when grey icons setting is enabled */ -body.grey-icons-enabled .board-backgrounds-list .board-background-select .background-box i.fa-check { - color: #7a7a7a; -} - -/* Prevent Grey Icons from affecting checkmarks in background color list */ -body.grey-icons-enabled .checkmark-no-grey { - filter: none !important; - -webkit-filter: none !important; -} - /* Mobile view styles - applied when isMiniScreen is true (iPhone, etc.) */ .board-list.mobile-view { height: calc(100vh - 120px); @@ -1143,62 +739,9 @@ body.grey-icons-enabled .checkmark-no-grey { #resetBtn { display: inline; } - -#resetBtn.filter-reset-btn { - background: #f44336; - color: #000; - border: none; - border-radius: 4px; - padding: 6px 12px; - cursor: pointer; - font-size: 14px; - display: inline-flex; - align-items: center; - gap: 6px; - transition: background 0.2s; -} - -#resetBtn.filter-reset-btn:hover { - background: #d32f2f; -} - -#resetBtn.filter-reset-btn .reset-icon { - font-size: 14px; -} - .js-board { display: block; - background-color: #999; /* Default gray background if no color class is applied */ - border-radius: 3px; /* Rounded corners for board items */ - overflow: hidden; /* Ensure children respect rounded corners */ - margin: 8px; /* Space between board items */ } - -/* Reset background for add-board button */ -.js-add-board { - background-color: transparent !important; - margin: 8px !important; /* Keep margin for add-board */ -} - -/* Apply board colors to li.js-board parent instead of just the link */ -.board-list .board-color-nephritis { background-color: #27ae60; } -.board-list .board-color-pomegranate { background-color: #c0392b; } -.board-list .board-color-belize { background-color: #2980b9; } -.board-list .board-color-wisteria { background-color: #8e44ad; } -.board-list .board-color-midnight { background-color: #2c3e50; } -.board-list .board-color-pumpkin { background-color: #e67e22; } -.board-list .board-color-moderatepink { background-color: #cd5a91; } -.board-list .board-color-strongcyan { background-color: #00aecc; } -.board-list .board-color-limegreen { background-color: #4bbf6b; } -.board-list .board-color-dark { background-color: #2c3e51; } -.board-list .board-color-relax { background-color: #27ae61; } -.board-list .board-color-corteza { background-color: #568ba2; } -.board-list .board-color-clearblue { background-color: #3498db; } -.board-list .board-color-natural { background-color: #596557; } -.board-list .board-color-modern { background-color: #2a80b8; } -.board-list .board-color-moderndark { background-color: #2a2a2a; } -.board-list .board-color-exodark { background-color: #222; } - .minicard-members { padding: 6px 0 6px 8px; width: 100%; diff --git a/client/components/boards/boardsList.jade b/client/components/boards/boardsList.jade index fc3ab582a..3d01c19c9 100644 --- a/client/components/boards/boardsList.jade +++ b/client/components/boards/boardsList.jade @@ -2,238 +2,158 @@ template(name="boardList") .wrapper .board-list-header - .boards-layout - // Left menu - .boards-left-menu - ul.menu - li(class="menu-item {{#if isSelectedMenu 'starred'}}active{{/if}}") - a.js-select-menu(data-type="starred") - span.menu-label - span.emoji-icon - i.fa.fa-star - | {{_ 'allboards.starred'}} - span.menu-count {{menuItemCount 'starred'}} - li(class="menu-item {{#if isSelectedMenu 'templates'}}active{{/if}}") - a.js-select-menu(data-type="templates") - span.menu-label - span.emoji-icon - i.fa.fa-clipboard - | {{_ 'allboards.templates'}} - span.menu-count {{menuItemCount 'templates'}} - li(class="menu-item {{#if isSelectedMenu 'remaining'}}active{{/if}}") - a.js-select-menu(data-type="remaining") - span.menu-label - span.emoji-icon - i.fa.fa-folder - | {{_ 'allboards.remaining'}} - span.menu-count {{menuItemCount 'remaining'}} - .workspaces-header - span - span.emoji-icon - i.fa.fa-folder-open - | {{_ 'allboards.workspaces'}} - a.js-add-workspace(title="{{_ 'allboards.add-workspace'}}") + - // Workspaces tree - +workspaceTree(nodes=workspacesTree selectedWorkspaceId=selectedWorkspaceId) + ul.AllBoardTeamsOrgs + li.AllBoardTeams + if userHasTeams + select.js-AllBoardTeams#jsAllBoardTeams("multiple") + option(value="-1") {{_ 'teams'}} : + each teamsDatas + option(value="{{teamId}}") {{_ teamDisplayName}} - // Existing filter by orgs/teams (kept) - ul.AllBoardTeamsOrgs - li.AllBoardTeams - if userHasTeams - select.js-AllBoardTeams#jsAllBoardTeams("multiple") - option(value="-1") {{_ 'teams'}} : - each teamsDatas - option(value="{{teamId}}") {{_ teamDisplayName}} + li.AllBoardOrgs + if userHasOrgs + select.js-AllBoardOrgs#jsAllBoardOrgs("multiple") + option(value="-1") {{_ 'organizations'}} : + each orgsDatas + option(value="{{orgId}}") {{orgDisplayName}} - li.AllBoardOrgs - if userHasOrgs - select.js-AllBoardOrgs#jsAllBoardOrgs("multiple") - option(value="-1") {{_ 'organizations'}} : - each orgsDatas - option(value="{{orgId}}") {{orgDisplayName}} + //li.AllBoardTemplates + // if userHasTemplates + // select.js-AllBoardTemplates#jsAllBoardTemplates("multiple") + // option(value="-1") {{_ 'templates'}} : + // each templatesDatas + // option(value="{{templateId}}") {{_ templateDisplayName}} - li.AllBoardBtns - div.AllBoardButtonsContainer - if userHasOrgsOrTeams - span.emoji-icon - i.fa.fa-search - input#filterBtn(type="button" value="{{_ 'filter'}}") - button#resetBtn.filter-reset-btn - span.reset-icon - span.emoji-icon - i.fa.fa-times-thin - span {{_ 'filter-clear'}} + li.AllBoardBtns + div.AllBoardButtonsContainer + if userHasOrgsOrTeams + i.fa.fa-filter + input#filterBtn(type="button" value="{{_ 'filter'}}") + input#resetBtn(type="button" value="{{_ 'filter-clear'}}") - // Right boards grid - .boards-right-grid - .boards-path-header - .path-left - span.path-icon.emoji-icon {{currentMenuPath.icon}} - span.path-text {{currentMenuPath.text}} - if BoardMultiSelection.isActive - span.multiselection-hint - span.emoji-icon - i.fa.fa-thumb-tack - | {{_ 'multi-selection-active'}} - .path-right - if canModifyBoards - if hasBoardsSelected - button.js-archive-selected-boards.board-header-btn - span.emoji-icon - i.fa.fa-archive - span {{_ 'archive-board'}} - button.js-duplicate-selected-boards.board-header-btn - span.emoji-icon - i.fa.fa-clipboard - span {{_ 'duplicate-board'}} - a.board-header-btn.js-multiselection-activate( - title="{{#if BoardMultiSelection.isActive}}{{_ 'multi-selection-on'}}{{else}}{{_ 'multi-selection'}}{{/if}}" - class="{{#if BoardMultiSelection.isActive}}emphasis{{/if}}") - span.emoji-icon - i.fa.fa-check-square-o - if BoardMultiSelection.isActive - a.board-header-btn-close.js-multiselection-reset(title="{{_ 'filter-clear'}}") - span.emoji-icon - i.fa.fa-times - ul.board-list.clearfix.js-boards(class="{{#if isMiniScreen}}mobile-view{{/if}} {{#if BoardMultiSelection.isActive}}is-multiselection-active{{/if}}") - li.js-add-board - if isSelectedMenu 'templates' - a.board-list-item.label(title="{{_ 'add-template-container'}}") - span.emoji-icon - i.fa.fa-plus - |  {{_ 'add-template-container'}} + ul.board-list.clearfix.js-boards(class="{{#if isMiniScreen}}mobile-view{{/if}}") + li.js-add-board + a.board-list-item.label(title="{{_ 'add-board'}}") + | {{_ 'add-board'}} + each boards + li(class="{{_id}}" class="{{#if isStarred}}starred{{/if}}" class=colorClass).js-board + if isInvited + .board-list-item + span.details + span.board-list-item-name= title + i.fa.js-star-board( + class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}" + title="{{_ 'star-board-title'}}") + p.board-list-item-desc {{_ 'just-invited'}} + button.js-accept-invite.primary {{_ 'accept'}} + button.js-decline-invite {{_ 'decline'}} + else + if $eq type "template-container" + a.js-open-board.template-container.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}") + span.details + span.board-list-item-name(title="{{_ 'template-container'}}") + +viewer + = title + i.fa.js-star-board( + class="fa-star{{#if isStarred}} is-star-active{{else}}-o{{/if}}" + title="{{_ 'star-board-title'}}") + p.board-list-item-desc + +viewer + = description + if hasSpentTimeCards + i.fa.js-has-spenttime-cards( + class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}" + title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}") + i.fa.board-handle( + class="fa-arrows" + title="{{_ 'drag-board'}}") + if isSandstorm + i.fa.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + i.fa.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") + else if isAdministrable + i.fa.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + i.fa.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") + else if currentUser.isAdmin + i.fa.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + i.fa.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") else - a.board-list-item.label(title="{{_ 'add-board'}}") - span.emoji-icon - i.fa.fa-plus - |  {{_ 'add-board'}} - each boards - li.js-board(class="{{_id}} {{#if isStarred}}starred{{/if}} {{colorClass}} {{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}", draggable="true") - if isInvited - .board-list-item - if BoardMultiSelection.isActive - .materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection( - class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}") - span.details - span.board-list-item-name= title - span.js-star-board( - class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}" - title="{{_ 'star-board-title'}}") - span.emoji-icon - | {{#if isStarred}}⭐{{else}}☆{{/if}} - p.board-list-item-desc {{_ 'just-invited'}} - button.js-accept-invite.primary {{_ 'accept'}} - button.js-decline-invite {{_ 'decline'}} - else - if $eq type "template-container" - .template-container.board-list-item - if BoardMultiSelection.isActive - .materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection( - class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}") - span.board-handle(title="{{_ 'drag-board'}}") - span.emoji-icon - i.fa.fa-arrows - - a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}") - span.details - span.board-list-item-name(title="{{_ 'template-container'}}") - +viewer - = title - p.board-list-item-desc - +viewer - = description - if hasSpentTimeCards - span.js-has-spenttime-cards( - class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}" - title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}") - span.emoji-icon - i.fa.fa-clock-o - span.js-star-board( - class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}" - title="{{_ 'star-board-title'}}") - span.emoji-icon - i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}") - else - .board-list-item - if BoardMultiSelection.isActive - .materialCheckBox.multi-selection-checkbox.js-toggle-board-multi-selection( - class="{{#if BoardMultiSelection.isSelected _id}}is-checked{{/if}}") - span.board-handle(title="{{_ 'drag-board'}}") - span.emoji-icon - i.fa.fa-arrows - - a.js-open-board(href="{{pathFor 'board' id=_id slug=slug}}") - span.details - span.board-list-item-name(title="{{_ 'board-drag-drop-reorder-or-click-open'}}") - +viewer - = title - unless currentSetting.hideBoardMemberList - if allowsBoardMemberList - .minicard-members - each member in boardMembers _id - a.name - +userAvatar(userId=member noRemove=true) - unless currentSetting.hideCardCounterList - if allowsCardCounterList - .minicard-lists.flex.flex-wrap - each list in boardLists _id - .item - | {{ list }} - p.board-list-item-desc - +viewer - = description - if hasSpentTimeCards - span.js-has-spenttime-cards( - class="{{#if hasOvertimeCards}}has-overtime-card-active{{else}}no-overtime-card-active{{/if}}" - title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}") - span.emoji-icon - i.fa.fa-clock-o - a.js-star-board( - class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}" - title="{{_ 'star-board-title'}}") - span.emoji-icon - i.fa(class="fa-star{{#unless isStarred}}-o{{/unless}}") + a.js-open-board.board-list-item(href="{{pathFor 'board' id=_id slug=slug}}") + span.details + span.board-list-item-name(title="{{_ 'board-drag-drop-reorder-or-click-open'}}") + +viewer + = title + unless currentSetting.hideBoardMemberList + if allowsBoardMemberList + .minicard-members + each member in boardMembers _id + a.name + +userAvatar(userId=member noRemove=true) + unless currentSetting.hideCardCounterList + if allowsCardCounterList + .minicard-lists.flex.flex-wrap + each list in boardLists _id + .item + | {{ list }} + a.js-star-board( + class="{{#if isStarred}}is-star-active{{else}}is-not-star-active{{/if}}" + title="{{_ 'star-board-title'}}") + | {{#if isStarred}}⭐{{else}}☆{{/if}} + p.board-list-item-desc + +viewer + = description + if hasSpentTimeCards + i.fa.js-has-spenttime-cards( + class="fa-circle{{#if hasOvertimeCards}} has-overtime-card-active{{else}} no-overtime-card-active{{/if}}" + title="{{#if hasOvertimeCards}}{{_ 'has-overtime-cards'}}{{else}}{{_ 'has-spenttime-cards'}}{{/if}}") + i.fa.board-handle( + class="fa-arrows" + title="{{_ 'drag-board'}}") + if isSandstorm + a.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + | 📋 + a.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") + | 📦 + else if isAdministrable + a.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + | 📋 + a.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") + | 📦 + else if currentUser.isAdmin + a.js-clone-board( + class="fa-clone" + title="{{_ 'duplicate-board'}}") + | 📋 + a.js-archive-board( + class="fa-archive" + title="{{_ 'archive-board'}}") + | 📦 template(name="boardListHeaderBar") h1 {{_ title }} //.board-header-btns.right - // - a.board-header-btn.js-open-archived-board - // - i.fa.fa-archive - // - span {{_ 'archives'}} - // - a.board-header-btn(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}") - // - i.fa.fa-clone - // - span {{_ 'templates'}} - -// Recursive template for workspaces tree -template(name="workspaceTree") - if nodes - ul.workspace-tree.js-workspace-tree - each nodes - li.workspace-node(class="{{#if $eq id selectedWorkspaceId}}active{{/if}}" data-workspace-id="{{id}}" draggable="true") - .workspace-node-content - span.workspace-drag-handle - span.emoji-icon - i.fa.fa-arrows - - a.js-select-workspace(data-id="{{id}}") - span.workspace-icon - if icon - +viewer - = icon - else - span.emoji-icon - i.fa.fa-folder - span.workspace-name= name - a.js-edit-workspace(data-id="{{id}}" title="{{_ 'allboards.edit-workspace'}}") - span.emoji-icon - i.fa.fa-pencil-square-o - span.workspace-count {{workspaceCount id}} - a.js-add-subworkspace(data-id="{{id}}" title="{{_ 'allboards.add-subworkspace'}}") + - if children - +workspaceTree(nodes=children selectedWorkspaceId=selectedWorkspaceId) + // a.board-header-btn.js-open-archived-board + // i.fa.fa-archive + // span {{_ 'archives'}} + // a.board-header-btn(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}") + // i.fa.fa-clone + // span {{_ 'templates'}} diff --git a/client/components/boards/boardsList.js b/client/components/boards/boardsList.js index 9fa21f806..db2ed2446 100644 --- a/client/components/boards/boardsList.js +++ b/client/components/boards/boardsList.js @@ -1,7 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; -import getSlug from 'limax'; const subManager = new SubsManager(); @@ -16,10 +14,7 @@ Template.boardList.helpers({ return Utils.isMiniScreen() && Session.get('currentBoard'); */ return true; }, - BoardMultiSelection() { - return BoardMultiSelection; - }, -}); +}) Template.boardListHeaderBar.events({ 'click .js-open-archived-board'() { @@ -27,7 +22,8 @@ Template.boardListHeaderBar.events({ }, }); -Template.boardList.events({}); +Template.boardList.events({ +}); Template.boardListHeaderBar.helpers({ title() { @@ -45,269 +41,143 @@ Template.boardListHeaderBar.helpers({ }, }); -Template.boardList.onCreated(function () { - Meteor.subscribe('setting'); - Meteor.subscribe('tableVisibilityModeSettings'); - this.selectedMenu = new ReactiveVar('starred'); - this.selectedWorkspaceIdVar = new ReactiveVar(null); - this.workspacesTreeVar = new ReactiveVar([]); - let currUser = ReactiveCache.getCurrentUser(); - let userLanguage; - if (currUser && currUser.profile) { - userLanguage = currUser.profile.language; - } - if (userLanguage) { - TAPi18n.setLanguage(userLanguage); - } - - this.reorderWorkspaces = (draggedSpaceId, targetSpaceId) => { - const tree = this.workspacesTreeVar.get(); - - // Helper to remove a space from tree - const removeSpace = (nodes, id) => { - for (let i = 0; i < nodes.length; i++) { - if (nodes[i].id === id) { - const removed = nodes.splice(i, 1)[0]; - return { tree: nodes, removed }; - } - if (nodes[i].children) { - const result = removeSpace(nodes[i].children, id); - if (result.removed) { - return { tree: nodes, removed: result.removed }; - } - } - } - return { tree: nodes, removed: null }; - }; - - // Helper to insert a space after target - const insertAfter = (nodes, targetId, spaceToInsert) => { - for (let i = 0; i < nodes.length; i++) { - if (nodes[i].id === targetId) { - nodes.splice(i + 1, 0, spaceToInsert); - return true; - } - if (nodes[i].children) { - if (insertAfter(nodes[i].children, targetId, spaceToInsert)) { - return true; - } - } - } - return false; - }; - - // Clone the tree - const newTree = EJSON.clone(tree); - - // Remove the dragged space - const { tree: treeAfterRemoval, removed } = removeSpace( - newTree, - draggedSpaceId, - ); - - if (removed) { - // Insert after target - insertAfter(treeAfterRemoval, targetSpaceId, removed); - - // Save the new tree - Meteor.call('setWorkspacesTree', treeAfterRemoval, (err) => { - if (err) console.error(err); - }); +BlazeComponent.extendComponent({ + onCreated() { + Meteor.subscribe('setting'); + Meteor.subscribe('tableVisibilityModeSettings'); + let currUser = ReactiveCache.getCurrentUser(); + let userLanguage; + if (currUser && currUser.profile) { + userLanguage = currUser.profile.language } - }; - - // Load workspaces tree reactively - this.autorun(() => { - const u = ReactiveCache.getCurrentUser(); - const tree = (u && u.profile && u.profile.boardWorkspacesTree) || []; - this.workspacesTreeVar.set(tree); - }); -}); - -Template.boardList.onRendered(function () { - // jQuery sortable is disabled in favor of HTML5 drag-and-drop for space management - // The old sortable code has been removed to prevent conflicts - /* OLD SORTABLE CODE - DISABLED - const itemsSelector = '.js-board:not(.placeholder)'; - - const $boards = this.$('.js-boards'); - $boards.sortable({ - connectWith: '.js-boards', - tolerance: 'pointer', - appendTo: '.board-list', - helper: 'clone', - distance: 7, - items: itemsSelector, - placeholder: 'board-wrapper placeholder', - start(evt, ui) { - ui.helper.css('z-index', 1000); - ui.placeholder.height(ui.helper.height()); - EscapeActions.executeUpTo('popup-close'); - }, - async stop(evt, ui) { - const prevBoardDom = ui.item.prev('.js-board').get(0); - const nextBoardDom = ui.item.next('.js-board').get(0); - const sortIndex = Utils.calculateIndex(prevBoardDom, nextBoardDom, 1); - - const boardDomElement = ui.item.get(0); - const board = Blaze.getData(boardDomElement); - $boards.sortable('cancel'); - const currentUser = ReactiveCache.getCurrentUser(); - if (currentUser && typeof currentUser.setBoardSortIndex === 'function') { - await currentUser.setBoardSortIndex(board._id, sortIndex.base); - } - }, - }); - - this.autorun(() => { - if (Utils.isTouchScreenOrShowDesktopDragHandles()) { - $boards.sortable({ - handle: '.board-handle', - }); + if (userLanguage) { + TAPi18n.setLanguage(userLanguage); } - }); - */ -}); + }, -Template.boardList.helpers({ + onRendered() { + const itemsSelector = '.js-board:not(.placeholder)'; + + const $boards = this.$('.js-boards'); + $boards.sortable({ + connectWith: '.js-boards', + tolerance: 'pointer', + appendTo: '.board-list', + helper: 'clone', + distance: 7, + items: itemsSelector, + placeholder: 'board-wrapper placeholder', + start(evt, ui) { + ui.helper.css('z-index', 1000); + ui.placeholder.height(ui.helper.height()); + EscapeActions.executeUpTo('popup-close'); + }, + stop(evt, ui) { + // To attribute the new index number, we need to get the DOM element + const prevBoardDom = ui.item.prev('.js-board').get(0); + const nextBoardDom = ui.item.next('.js-board').get(0); + const sortIndex = Utils.calculateIndex(prevBoardDom, nextBoardDom, 1); + + const boardDomElement = ui.item.get(0); + const board = Blaze.getData(boardDomElement); + // Normally the jquery-ui sortable library moves the dragged DOM element + // to its new position, which disrupts Blaze reactive updates mechanism + // (especially when we move the last card of a list, or when multiple + // users move some cards at the same time). To prevent these UX glitches + // we ask sortable to gracefully cancel the move, and to put back the + // DOM in its initial state. The card move is then handled reactively by + // Blaze with the below query. + $boards.sortable('cancel'); + const currentUser = ReactiveCache.getCurrentUser(); + if (currentUser && typeof currentUser.setBoardSortIndex === 'function') { + currentUser.setBoardSortIndex(board._id, sortIndex.base); + } + }, + }); + + // Disable drag-dropping if the current user is not a board member or is comment only + this.autorun(() => { + if (Utils.isTouchScreenOrShowDesktopDragHandles()) { + $boards.sortable({ + handle: '.board-handle', + }); + } + }); + }, userHasTeams() { - if (ReactiveCache.getCurrentUser()?.teams?.length > 0) return true; - else return false; + if (ReactiveCache.getCurrentUser()?.teams?.length > 0) + return true; + else + return false; }, teamsDatas() { - const teams = ReactiveCache.getCurrentUser()?.teams; + const teams = ReactiveCache.getCurrentUser()?.teams if (teams) - return teams.sort((a, b) => - a.teamDisplayName.localeCompare(b.teamDisplayName), - ); - else return []; + return teams.sort((a, b) => a.teamDisplayName.localeCompare(b.teamDisplayName)); + else + return []; }, userHasOrgs() { - if (ReactiveCache.getCurrentUser()?.orgs?.length > 0) return true; - else return false; + if (ReactiveCache.getCurrentUser()?.orgs?.length > 0) + return true; + else + return false; }, orgsDatas() { const orgs = ReactiveCache.getCurrentUser()?.orgs; if (orgs) - return orgs.sort((a, b) => - a.orgDisplayName.localeCompare(b.orgDisplayName), - ); - else return []; + return orgs.sort((a, b) => a.orgDisplayName.localeCompare(b.orgDisplayName)); + else + return []; }, userHasOrgsOrTeams() { - const tpl = Template.instance(); - const userHasOrgs = ReactiveCache.getCurrentUser()?.orgs?.length > 0; - const userHasTeams = ReactiveCache.getCurrentUser()?.teams?.length > 0; - return userHasOrgs || userHasTeams; - }, - currentMenuPath() { - try { - const tpl = Template.instance(); - const selectedMenuVar = tpl.selectedMenu; - if (!selectedMenuVar || typeof selectedMenuVar.get !== 'function') { - return { icon: '🗂️', text: 'Workspaces' }; - } - const sel = selectedMenuVar.get(); - const currentUser = ReactiveCache.getCurrentUser(); - - // Helper function to safely get translation or fallback - const safeTranslate = (key, fallback) => { - try { - return TAPi18n.__(key) || fallback; - } catch (e) { - return fallback; - } - }; - - // Helper to find space by id in tree - const findSpaceById = (nodes, targetId, path = []) => { - if (!nodes || !Array.isArray(nodes)) return null; - for (const node of nodes) { - if (node.id === targetId) { - return [...path, node]; - } - if (node.children && node.children.length > 0) { - const result = findSpaceById(node.children, targetId, [ - ...path, - node, - ]); - if (result) return result; - } - } - return null; - }; - - if (sel === 'starred') { - return { icon: '⭐', text: safeTranslate('allboards.starred', 'Starred') }; - } else if (sel === 'templates') { - return { icon: '📋', text: safeTranslate('allboards.templates', 'Templates') }; - } else if (sel === 'remaining') { - return { icon: '📂', text: safeTranslate('allboards.remaining', 'Remaining') }; - } else { - // sel is a workspaceId, build path - if (!tpl.workspacesTreeVar || typeof tpl.workspacesTreeVar.get !== 'function') { - return { icon: '🗂️', text: safeTranslate('allboards.workspaces', 'Workspaces') }; - } - const tree = tpl.workspacesTreeVar.get(); - const spacePath = findSpaceById(tree, sel); - if (spacePath && spacePath.length > 0) { - const pathText = spacePath.map((s) => s.name).join(' / '); - return { - icon: '🗂️', - text: `${safeTranslate('allboards.workspaces', 'Workspaces')} / ${pathText}`, - }; - } - return { icon: '🗂️', text: safeTranslate('allboards.workspaces', 'Workspaces') }; - } - } catch (error) { - console.error('Error in currentMenuPath:', error); - return { icon: '🗂️', text: 'Workspaces' }; - } + const ret = this.userHasOrgs() || this.userHasTeams(); + return ret; }, boards() { - const tpl = Template.instance(); let query = { + // { type: 'board' }, + // { type: { $in: ['board','template-container'] } }, $and: [ { archived: false }, { type: { $in: ['board', 'template-container'] } }, - { title: { $not: { $regex: /^\^.*\^$/ } } }, - ], + { $or: [] }, + { title: { $not: { $regex: /^\^.*\^$/ } } } + ] }; - const membershipOrs = []; - let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne( - 'tableVisibilityMode-allowPrivateOnly', - ); + let allowPrivateVisibilityOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly'); if (FlowRouter.getRouteName() === 'home') { - membershipOrs.push({ 'members.userId': Meteor.userId() }); + query.$and[2].$or.push({ 'members.userId': Meteor.userId() }); - if ( - allowPrivateVisibilityOnly !== undefined && - allowPrivateVisibilityOnly.booleanValue - ) { - query.$and.push({ permission: 'private' }); + if (allowPrivateVisibilityOnly !== undefined && allowPrivateVisibilityOnly.booleanValue) { + query.$and.push({ 'permission': 'private' }); } const currUser = ReactiveCache.getCurrentUser(); let orgIdsUserBelongs = currUser?.orgIdsUserBelongs() || ''; if (orgIdsUserBelongs) { let orgsIds = orgIdsUserBelongs.split(','); - membershipOrs.push({ 'orgs.orgId': { $in: orgsIds } }); + // for(let i = 0; i < orgsIds.length; i++){ + // query.$and[2].$or.push({'orgs.orgId': orgsIds[i]}); + // } + + //query.$and[2].$or.push({'orgs': {$elemMatch : {orgId: orgsIds[0]}}}); + query.$and[2].$or.push({ 'orgs.orgId': { $in: orgsIds } }); } let teamIdsUserBelongs = currUser?.teamIdsUserBelongs() || ''; if (teamIdsUserBelongs) { let teamsIds = teamIdsUserBelongs.split(','); - membershipOrs.push({ 'teams.teamId': { $in: teamsIds } }); + // for(let i = 0; i < teamsIds.length; i++){ + // query.$or[2].$or.push({'teams.teamId': teamsIds[i]}); + // } + //query.$and[2].$or.push({'teams': { $elemMatch : {teamId: teamsIds[0]}}}); + query.$and[2].$or.push({ 'teams.teamId': { $in: teamsIds } }); } - if (membershipOrs.length) { - query.$and.splice(2, 0, { $or: membershipOrs }); - } - } else if ( - allowPrivateVisibilityOnly !== undefined && - !allowPrivateVisibilityOnly.booleanValue - ) { + } + else if (allowPrivateVisibilityOnly !== undefined && !allowPrivateVisibilityOnly.booleanValue) { query = { archived: false, //type: { $in: ['board','template-container'] }, @@ -318,36 +188,11 @@ Template.boardList.helpers({ const boards = ReactiveCache.getBoards(query, {}); const currentUser = ReactiveCache.getCurrentUser(); - let list = boards; - // Apply left menu filtering - const sel = tpl.selectedMenu.get(); - const assignments = - (currentUser && - currentUser.profile && - currentUser.profile.boardWorkspaceAssignments) || - {}; - if (sel === 'starred') { - list = list.filter((b) => currentUser && currentUser.hasStarred(b._id)); - } else if (sel === 'templates') { - list = list.filter((b) => b.type === 'template-container'); - } else if (sel === 'remaining') { - // Show boards not in any workspace AND not templates - // Keep starred boards visible in Remaining too - list = list.filter( - (b) => !assignments[b._id] && b.type !== 'template-container', - ); - } else { - // assume sel is a workspaceId - // Keep starred boards visible in their workspace too - list = list.filter((b) => assignments[b._id] === sel); - } - if (currentUser && typeof currentUser.sortBoardsForUser === 'function') { - return currentUser.sortBoardsForUser(list); + return currentUser.sortBoardsForUser(boards); } - return list - .slice() - .sort((a, b) => (a.title || '').localeCompare(b.title || '')); + // Fallback: deterministic title sort when no user mapping is available (e.g., public page) + return boards.slice().sort((a, b) => (a.title || '').localeCompare(b.title || '')); }, boardLists(boardId) { /* Bug Board icons random dance https://github.com/wekan/wekan/issues/4214 @@ -372,601 +217,151 @@ Template.boardList.helpers({ isStarred() { const user = ReactiveCache.getCurrentUser(); - return user && user.hasStarred(this._id); + return user && user.hasStarred(this.currentData()._id); }, isAdministrable() { const user = ReactiveCache.getCurrentUser(); - return user && user.isBoardAdmin(this._id); + return user && user.isBoardAdmin(this.currentData()._id); }, hasOvertimeCards() { - return this.hasOvertimeCards(); + return this.currentData().hasOvertimeCards(); }, hasSpentTimeCards() { - return this.hasSpentTimeCards(); + return this.currentData().hasSpentTimeCards(); }, isInvited() { const user = ReactiveCache.getCurrentUser(); - return user && user.isInvitedTo(this._id); + return user && user.isInvitedTo(this.currentData()._id); }, - // Helpers for templates - workspacesTree() { - return Template.instance().workspacesTreeVar.get(); - }, - selectedWorkspaceId() { - return Template.instance().selectedWorkspaceIdVar.get(); - }, - isSelectedMenu(type) { - return Template.instance().selectedMenu.get() === type; - }, - isSpaceSelected(id) { - return Template.instance().selectedWorkspaceIdVar.get() === id; - }, - menuItemCount(type) { - const currentUser = ReactiveCache.getCurrentUser(); - const assignments = - (currentUser && - currentUser.profile && - currentUser.profile.boardWorkspaceAssignments) || - {}; - - // Get all boards for counting - let query = { - $and: [ - { archived: false }, - { type: { $in: ['board', 'template-container'] } }, - { $or: [{ 'members.userId': Meteor.userId() }] }, - { title: { $not: { $regex: /^\^.*\^$/ } } }, - ], - }; - const allBoards = ReactiveCache.getBoards(query, {}); - - if (type === 'starred') { - return allBoards.filter( - (b) => currentUser && currentUser.hasStarred(b._id), - ).length; - } else if (type === 'templates') { - return allBoards.filter((b) => b.type === 'template-container').length; - } else if (type === 'remaining') { - // Count boards not in any workspace AND not templates - // Include starred boards (they appear in both Starred and Remaining) - return allBoards.filter( - (b) => !assignments[b._id] && b.type !== 'template-container', - ).length; - } - return 0; - }, - workspaceCount(workspaceId) { - const currentUser = ReactiveCache.getCurrentUser(); - const assignments = - (currentUser && - currentUser.profile && - currentUser.profile.boardWorkspaceAssignments) || - {}; - - // Get all boards for counting - let query = { - $and: [ - { archived: false }, - { type: { $in: ['board', 'template-container'] } }, - { $or: [{ 'members.userId': Meteor.userId() }] }, - { title: { $not: { $regex: /^\^.*\^$/ } } }, - ], - }; - const allBoards = ReactiveCache.getBoards(query, {}); - - // Count boards directly assigned to this space (not including children) - return allBoards.filter((b) => assignments[b._id] === workspaceId).length; - }, - canModifyBoards() { - const currentUser = ReactiveCache.getCurrentUser(); - return currentUser && !currentUser.isCommentOnly(); - }, - hasBoardsSelected() { - return BoardMultiSelection.count() > 0; - }, -}); - -Template.workspaceTree.helpers({ - workspaceCount(workspaceId) { - const currentUser = ReactiveCache.getCurrentUser(); - const assignments = - (currentUser && - currentUser.profile && - currentUser.profile.boardWorkspaceAssignments) || - {}; - - let query = { - $and: [ - { archived: false }, - { type: { $in: ['board', 'template-container'] } }, - { $or: [{ 'members.userId': Meteor.userId() }] }, - { title: { $not: { $regex: /^\^.*\^$/ } } }, - ], - }; - const allBoards = ReactiveCache.getBoards(query, {}); - - return allBoards.filter((b) => assignments[b._id] === workspaceId).length; - }, -}); - -Template.boardList.events({ - 'click .js-select-menu'(evt, tpl) { - const type = evt.currentTarget.getAttribute('data-type'); - tpl.selectedWorkspaceIdVar.set(null); - tpl.selectedMenu.set(type); - }, - 'click .js-select-workspace'(evt, tpl) { - const id = evt.currentTarget.getAttribute('data-id'); - tpl.selectedWorkspaceIdVar.set(id); - tpl.selectedMenu.set(id); - }, - 'click .js-add-workspace'(evt, tpl) { - evt.preventDefault(); - const name = prompt( - TAPi18n.__('allboards.add-workspace-prompt') || 'New Space name', - ); - if (name && name.trim()) { - Meteor.call( - 'createWorkspace', - { parentId: null, name: name.trim() }, - (err, res) => { - if (err) console.error(err); + events() { + return [ + { + 'click .js-add-board': Popup.open('createBoard'), + 'click .js-star-board'(evt) { + const boardId = this.currentData()._id; + ReactiveCache.getCurrentUser().toggleBoardStar(boardId); + evt.preventDefault(); }, - ); - } - }, - 'click .js-add-board'(evt, tpl) { - // Store the currently selected workspace/menu for board creation - const selectedWorkspaceId = tpl.selectedWorkspaceIdVar.get(); - const selectedMenu = tpl.selectedMenu.get(); - - if (selectedWorkspaceId) { - Session.set('createBoardInWorkspace', selectedWorkspaceId); - } else { - Session.set('createBoardInWorkspace', null); - } - - // Open different popup based on context - if (selectedMenu === 'templates') { - Popup.open('createTemplateContainer')(evt); - } else { - Popup.open('createBoard')(evt); - } - }, - 'click .js-star-board'(evt) { - evt.preventDefault(); - evt.stopPropagation(); - const boardId = this._id; - if (boardId) { - Meteor.call('toggleBoardStar', boardId); - } - }, - // HTML5 DnD from boards to spaces - 'dragstart .js-board'(evt) { - const boardId = this._id; - - // Support multi-drag - if ( - BoardMultiSelection.isActive() && - BoardMultiSelection.isSelected(boardId) - ) { - const selectedIds = BoardMultiSelection.getSelectedBoardIds(); - try { - evt.originalEvent.dataTransfer.setData( - 'text/plain', - JSON.stringify(selectedIds), - ); - evt.originalEvent.dataTransfer.setData( - 'application/x-board-multi', - 'true', - ); - } catch (e) {} - } else { - try { - evt.originalEvent.dataTransfer.setData('text/plain', boardId); - } catch (e) {} - } - }, - 'click .js-clone-board'(evt) { - if (confirm(TAPi18n.__('duplicate-board-confirm'))) { - let title = - getSlug(ReactiveCache.getBoard(this._id).title) || - 'cloned-board'; - Meteor.call( - 'copyBoard', - this._id, - { - sort: ReactiveCache.getBoards({ archived: false }).length, - type: 'board', - title: ReactiveCache.getBoard(this._id).title, - }, - (err, res) => { - if (err) { - console.error(err); - } else { - Session.set('fromBoard', null); - subManager.subscribe('board', res, false); - FlowRouter.go('board', { - id: res, - slug: title, - }); + 'click .js-clone-board'(evt) { + if (confirm(TAPi18n.__('duplicate-board-confirm'))) { + let title = + getSlug(ReactiveCache.getBoard(this.currentData()._id).title) || + 'cloned-board'; + Meteor.call( + 'copyBoard', + this.currentData()._id, + { + sort: ReactiveCache.getBoards({ archived: false }).length, + type: 'board', + title: ReactiveCache.getBoard(this.currentData()._id).title, + }, + (err, res) => { + if (err) { + console.error(err); + } else { + Session.set('fromBoard', null); + subManager.subscribe('board', res, false); + FlowRouter.go('board', { + id: res, + slug: title, + }); + } + }, + ); + evt.preventDefault(); + } + }, + 'click .js-archive-board'(evt) { + if (confirm(TAPi18n.__('archive-board-confirm'))) { + const boardId = this.currentData()._id; + Meteor.call('archiveBoard', boardId); + evt.preventDefault(); + } + }, + 'click .js-accept-invite'() { + const boardId = this.currentData()._id; + Meteor.call('acceptInvite', boardId); + }, + 'click .js-decline-invite'() { + const boardId = this.currentData()._id; + Meteor.call('quitBoard', boardId, (err, ret) => { + if (!err && ret) { + Meteor.call('acceptInvite', boardId); + FlowRouter.go('home'); + } + }); + }, + 'click #resetBtn'(event) { + let allBoards = document.getElementsByClassName("js-board"); + let currBoard; + for (let i = 0; i < allBoards.length; i++) { + currBoard = allBoards[i]; + currBoard.style.display = "block"; + } + }, + 'click #filterBtn'(event) { + event.preventDefault(); + let selectedTeams = document.querySelectorAll('#jsAllBoardTeams option:checked'); + let selectedTeamsValues = Array.from(selectedTeams).map(function (elt) { return elt.value }); + let index = selectedTeamsValues.indexOf("-1"); + if (index > -1) { + selectedTeamsValues.splice(index, 1); + } + + let selectedOrgs = document.querySelectorAll('#jsAllBoardOrgs option:checked'); + let selectedOrgsValues = Array.from(selectedOrgs).map(function (elt) { return elt.value }); + index = selectedOrgsValues.indexOf("-1"); + if (index > -1) { + selectedOrgsValues.splice(index, 1); + } + + if (selectedTeamsValues.length > 0 || selectedOrgsValues.length > 0) { + const query = { + $and: [ + { archived: false }, + { type: 'board' }, + { $or: [] } + ] + }; + if (selectedTeamsValues.length > 0) { + query.$and[2].$or.push({ 'teams.teamId': { $in: selectedTeamsValues } }); + } + if (selectedOrgsValues.length > 0) { + query.$and[2].$or.push({ 'orgs.orgId': { $in: selectedOrgsValues } }); + } + + let filteredBoards = ReactiveCache.getBoards(query, {}); + let allBoards = document.getElementsByClassName("js-board"); + let currBoard; + if (filteredBoards.length > 0) { + let currBoardId; + let found; + for (let i = 0; i < allBoards.length; i++) { + currBoard = allBoards[i]; + currBoardId = currBoard.classList[0]; + found = filteredBoards.find(function (board) { + return board._id == currBoardId; + }); + + if (found !== undefined) + currBoard.style.display = "block"; + else + currBoard.style.display = "none"; + } + } + else { + for (let i = 0; i < allBoards.length; i++) { + currBoard = allBoards[i]; + currBoard.style.display = "none"; + } + } } }, - ); - evt.preventDefault(); - } - }, - 'click .js-archive-board'(evt) { - if (confirm(TAPi18n.__('archive-board-confirm'))) { - const boardId = this._id; - Meteor.call('archiveBoard', boardId); - evt.preventDefault(); - } - }, - 'click .js-accept-invite'() { - const boardId = this._id; - Meteor.call('acceptInvite', boardId); - }, - 'click .js-decline-invite'() { - const boardId = this._id; - Meteor.call('quitBoard', boardId, (err, ret) => { - if (!err && ret) { - Meteor.call('acceptInvite', boardId); - FlowRouter.go('home'); - } - }); - }, - 'click .js-multiselection-activate'(evt) { - evt.preventDefault(); - if (BoardMultiSelection.isActive()) { - BoardMultiSelection.disable(); - } else { - BoardMultiSelection.activate(); - } - }, - 'click .js-multiselection-reset'(evt) { - evt.preventDefault(); - BoardMultiSelection.disable(); - }, - 'click .js-toggle-board-multi-selection'(evt) { - evt.preventDefault(); - evt.stopPropagation(); - const boardId = this._id; - BoardMultiSelection.toogle(boardId); - }, - 'click .js-archive-selected-boards'(evt) { - evt.preventDefault(); - const selectedBoards = BoardMultiSelection.getSelectedBoardIds(); - if ( - selectedBoards.length > 0 && - confirm(TAPi18n.__('archive-board-confirm')) - ) { - selectedBoards.forEach((boardId) => { - Meteor.call('archiveBoard', boardId); - }); - BoardMultiSelection.reset(); - } - }, - 'click .js-duplicate-selected-boards'(evt) { - evt.preventDefault(); - const selectedBoards = BoardMultiSelection.getSelectedBoardIds(); - if ( - selectedBoards.length > 0 && - confirm(TAPi18n.__('duplicate-board-confirm')) - ) { - selectedBoards.forEach((boardId) => { - const board = ReactiveCache.getBoard(boardId); - if (board) { - Meteor.call( - 'copyBoard', - boardId, - { - sort: ReactiveCache.getBoards({ archived: false }).length, - type: 'board', - title: board.title, - }, - (err, res) => { - if (err) console.error(err); - }, - ); - } - }); - BoardMultiSelection.reset(); - } - }, - 'click #resetBtn'(event) { - let allBoards = document.getElementsByClassName('js-board'); - let currBoard; - for (let i = 0; i < allBoards.length; i++) { - currBoard = allBoards[i]; - currBoard.style.display = 'block'; - } - }, - 'click #filterBtn'(event) { - event.preventDefault(); - let selectedTeams = document.querySelectorAll( - '#jsAllBoardTeams option:checked', - ); - let selectedTeamsValues = Array.from(selectedTeams).map( - function (elt) { - return elt.value; }, - ); - let index = selectedTeamsValues.indexOf('-1'); - if (index > -1) { - selectedTeamsValues.splice(index, 1); - } - - let selectedOrgs = document.querySelectorAll( - '#jsAllBoardOrgs option:checked', - ); - let selectedOrgsValues = Array.from(selectedOrgs).map(function (elt) { - return elt.value; - }); - index = selectedOrgsValues.indexOf('-1'); - if (index > -1) { - selectedOrgsValues.splice(index, 1); - } - - if (selectedTeamsValues.length > 0 || selectedOrgsValues.length > 0) { - const query = { - $and: [{ archived: false }, { type: 'board' }], - }; - const ors = []; - if (selectedTeamsValues.length > 0) { - ors.push({ 'teams.teamId': { $in: selectedTeamsValues } }); - } - if (selectedOrgsValues.length > 0) { - ors.push({ 'orgs.orgId': { $in: selectedOrgsValues } }); - } - if (ors.length) { - query.$and.push({ $or: ors }); - } - - let filteredBoards = ReactiveCache.getBoards(query, {}); - let allBoards = document.getElementsByClassName('js-board'); - let currBoard; - if (filteredBoards.length > 0) { - let currBoardId; - let found; - for (let i = 0; i < allBoards.length; i++) { - currBoard = allBoards[i]; - currBoardId = currBoard.classList[0]; - found = filteredBoards.find(function (board) { - return board._id == currBoardId; - }); - - if (found !== undefined) currBoard.style.display = 'block'; - else currBoard.style.display = 'none'; - } - } else { - for (let i = 0; i < allBoards.length; i++) { - currBoard = allBoards[i]; - currBoard.style.display = 'none'; - } - } - } + ]; }, - 'click .js-edit-workspace'(evt, tpl) { - evt.preventDefault(); - evt.stopPropagation(); - const workspaceId = evt.currentTarget.getAttribute('data-id'); - - // Find the space in the tree - const findSpace = (nodes, id) => { - for (const node of nodes) { - if (node.id === id) return node; - if (node.children) { - const found = findSpace(node.children, id); - if (found) return found; - } - } - return null; - }; - - const tree = tpl.workspacesTreeVar.get(); - const space = findSpace(tree, workspaceId); - - if (space) { - const newName = prompt( - TAPi18n.__('allboards.edit-workspace-name') || 'Space name:', - space.name, - ); - const newIcon = prompt( - TAPi18n.__('allboards.edit-workspace-icon') || - 'Space icon (markdown):', - space.icon || '📁', - ); - - if (newName !== null && newName.trim()) { - // Update space in tree - const updateSpaceInTree = (nodes, id, updates) => { - return nodes.map((node) => { - if (node.id === id) { - return { ...node, ...updates }; - } - if (node.children) { - return { - ...node, - children: updateSpaceInTree(node.children, id, updates), - }; - } - return node; - }); - }; - - const updatedTree = updateSpaceInTree(tree, workspaceId, { - name: newName.trim(), - icon: newIcon || '📁', - }); - - Meteor.call('setWorkspacesTree', updatedTree, (err) => { - if (err) console.error(err); - }); - } - } - }, - 'click .js-add-subworkspace'(evt) { - evt.preventDefault(); - evt.stopPropagation(); - const parentId = evt.currentTarget.getAttribute('data-id'); - const name = prompt( - TAPi18n.__('allboards.add-subworkspace-prompt') || 'Subspace name:', - ); - - if (name && name.trim()) { - Meteor.call( - 'createWorkspace', - { parentId, name: name.trim() }, - (err) => { - if (err) console.error(err); - }, - ); - } - }, - 'dragstart .workspace-node'(evt) { - const workspaceId = - evt.currentTarget.getAttribute('data-workspace-id'); - evt.originalEvent.dataTransfer.effectAllowed = 'move'; - evt.originalEvent.dataTransfer.setData( - 'application/x-workspace-id', - workspaceId, - ); - - // Create a better drag image - const dragImage = evt.currentTarget.cloneNode(true); - dragImage.style.position = 'absolute'; - dragImage.style.top = '-9999px'; - dragImage.style.opacity = '0.8'; - document.body.appendChild(dragImage); - evt.originalEvent.dataTransfer.setDragImage(dragImage, 0, 0); - setTimeout(() => document.body.removeChild(dragImage), 0); - - evt.currentTarget.classList.add('dragging'); - }, - 'dragend .workspace-node'(evt) { - evt.currentTarget.classList.remove('dragging'); - document.querySelectorAll('.workspace-node').forEach((el) => { - el.classList.remove('drag-over'); - }); - }, - 'dragover .workspace-node'(evt) { - evt.preventDefault(); - evt.stopPropagation(); - - const draggingEl = document.querySelector('.workspace-node.dragging'); - const targetEl = evt.currentTarget; - - // Allow dropping boards on any space - // Or allow dropping spaces on other spaces (but not on itself or descendants) - if ( - !draggingEl || - (targetEl !== draggingEl && !draggingEl.contains(targetEl)) - ) { - evt.originalEvent.dataTransfer.dropEffect = 'move'; - targetEl.classList.add('drag-over'); - } - }, - 'dragleave .workspace-node'(evt) { - evt.currentTarget.classList.remove('drag-over'); - }, - 'drop .workspace-node'(evt, tpl) { - evt.preventDefault(); - evt.stopPropagation(); - - const targetEl = evt.currentTarget; - targetEl.classList.remove('drag-over'); - - // Check what's being dropped - board or workspace - const draggedWorkspaceId = evt.originalEvent.dataTransfer.getData( - 'application/x-workspace-id', - ); - const isMultiBoard = evt.originalEvent.dataTransfer.getData( - 'application/x-board-multi', - ); - const boardData = - evt.originalEvent.dataTransfer.getData('text/plain'); - - if (draggedWorkspaceId && !boardData) { - // This is a workspace reorder operation - const targetWorkspaceId = - targetEl.getAttribute('data-workspace-id'); - - if (draggedWorkspaceId !== targetWorkspaceId) { - tpl.reorderWorkspaces(draggedWorkspaceId, targetWorkspaceId); - } - } else if (boardData) { - // This is a board assignment operation - // Get the workspace ID directly from the dropped workspace-node's data-workspace-id attribute - const workspaceId = targetEl.getAttribute('data-workspace-id'); - - if (workspaceId) { - if (isMultiBoard) { - // Multi-board drag - try { - const boardIds = JSON.parse(boardData); - boardIds.forEach((boardId) => { - Meteor.call('assignBoardToWorkspace', boardId, workspaceId); - }); - } catch (e) { - // Error parsing multi-board data - } - } else { - // Single board drag - Meteor.call('assignBoardToWorkspace', boardData, workspaceId); - } - } - } - }, - 'dragover .js-select-menu'(evt) { - evt.preventDefault(); - evt.stopPropagation(); - - const menuType = evt.currentTarget.getAttribute('data-type'); - // Only allow drop on "remaining" menu to unassign boards from spaces - if (menuType === 'remaining') { - evt.originalEvent.dataTransfer.dropEffect = 'move'; - evt.currentTarget.classList.add('drag-over'); - } - }, - 'dragleave .js-select-menu'(evt) { - evt.currentTarget.classList.remove('drag-over'); - }, - 'drop .js-select-menu'(evt) { - evt.preventDefault(); - evt.stopPropagation(); - - const menuType = evt.currentTarget.getAttribute('data-type'); - evt.currentTarget.classList.remove('drag-over'); - - // Only handle drops on "remaining" menu - if (menuType !== 'remaining') return; - - const isMultiBoard = evt.originalEvent.dataTransfer.getData( - 'application/x-board-multi', - ); - const boardData = - evt.originalEvent.dataTransfer.getData('text/plain'); - - if (boardData) { - if (isMultiBoard) { - // Multi-board drag - unassign all from workspaces - try { - const boardIds = JSON.parse(boardData); - boardIds.forEach((boardId) => { - Meteor.call('unassignBoardFromWorkspace', boardId); - }); - } catch (e) { - // Error parsing multi-board data - } - } else { - // Single board drag - unassign from workspace - Meteor.call('unassignBoardFromWorkspace', boardData); - } - } - }, -}); +}).register('boardList'); diff --git a/client/components/boards/calendarView.css b/client/components/boards/calendarView.css deleted file mode 100644 index 2b70a76cf..000000000 --- a/client/components/boards/calendarView.css +++ /dev/null @@ -1,43 +0,0 @@ -.calendar-view .fc { - --fc-button-text-color: #333; - --fc-button-bg-color: #f5f5f5; - --fc-button-border-color: rgba(0, 0, 0, 0.2); - --fc-button-hover-bg-color: #e6e6e6; - --fc-button-hover-border-color: rgba(0, 0, 0, 0.25); - --fc-button-active-bg-color: #d9d9d9; - --fc-button-active-border-color: rgba(0, 0, 0, 0.3); -} - -.calendar-view .fc .fc-button-primary { - text-shadow: 0 1px 1px rgba(255, 255, 255, 0.75); - box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.2), - 0 1px 2px rgba(0, 0, 0, 0.05); - background-image: linear-gradient(to bottom, #fff 0%, #e6e6e6 100%); -} - -.calendar-view .fc .fc-button-primary:focus, -.calendar-view .fc .fc-button-primary:not(:disabled).fc-button-active, -.calendar-view .fc .fc-button-primary:not(:disabled):active { - box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.15), - 0 1px 2px rgba(0, 0, 0, 0.05); -} - -.calendar-create-close { - min-height: auto !important; - min-width: auto !important; - width: 32px; - height: 32px; - padding: 0 !important; - border: 0 !important; - background: transparent !important; - box-shadow: none !important; - color: #666 !important; - opacity: 0.8; -} - -.calendar-create-close:hover, -.calendar-create-close:focus { - background: transparent !important; - color: #111 !important; - opacity: 1; -} diff --git a/client/components/boards/miniboard.jade b/client/components/boards/miniboard.jade index f0a1ea367..d1fb0b075 100644 --- a/client/components/boards/miniboard.jade +++ b/client/components/boards/miniboard.jade @@ -3,7 +3,6 @@ template(name="miniboard") class="minicard-{{colorClass}}") .minicard-title .handle - span.drag-handle(title="{{_ 'dragBoard'}}") - i.fa.fa-arrows + .fa.fa-arrows +viewer = title diff --git a/client/components/boards/originalPositionsView.css b/client/components/boards/originalPositionsView.css index c2e1a3405..ec3abd4c5 100644 --- a/client/components/boards/originalPositionsView.css +++ b/client/components/boards/originalPositionsView.css @@ -164,32 +164,32 @@ margin: 5px 0; padding: 10px; } - + .original-positions-header { flex-direction: column; align-items: stretch; gap: 8px; } - + .original-positions-header .btn { justify-content: center; } - + .original-positions-filters .btn-group { justify-content: center; } - + .original-position-item-header { flex-wrap: wrap; gap: 6px; } - + .entity-name { flex: 1; min-width: 0; word-break: break-word; } - + .original-position-item-details { margin-left: 0; margin-top: 8px; @@ -203,60 +203,60 @@ border-color: #4a5568; color: #e2e8f0; } - + .original-positions-content { background-color: #1a202c; border-color: #4a5568; } - + .original-position-item { background-color: #2d3748; border-color: #4a5568; color: #e2e8f0; } - + .original-position-item:hover { background-color: #4a5568; border-color: #718096; } - + .original-position-item-header { color: #e2e8f0; } - + .original-position-item-header i { color: #a0aec0; } - + .entity-name { color: #e2e8f0; } - + .entity-id { color: #a0aec0; } - + .original-position-description { color: #e2e8f0; } - + .original-title { background-color: #4a5568; color: #a0aec0; } - + .original-title strong { color: #e2e8f0; } - + .original-position-date { color: #a0aec0; } - + .no-original-positions { color: #a0aec0; } - + .no-original-positions i { color: #718096; } diff --git a/client/components/boards/originalPositionsView.html b/client/components/boards/originalPositionsView.html new file mode 100644 index 000000000..6a58beeb0 --- /dev/null +++ b/client/components/boards/originalPositionsView.html @@ -0,0 +1,82 @@ + diff --git a/client/components/boards/originalPositionsView.jade b/client/components/boards/originalPositionsView.jade deleted file mode 100644 index 47bd184f3..000000000 --- a/client/components/boards/originalPositionsView.jade +++ /dev/null @@ -1,57 +0,0 @@ -template(name="originalPositionsView") - .original-positions-view - .original-positions-header - button.btn.btn-sm.btn-outline-secondary.js-toggle-original-positions - i.fa.fa-history - if isShowingOriginalPositions - | Hide Original Positions - else - | Show Original Positions - - if isShowingOriginalPositions - button.btn.btn-sm.btn-outline-primary.js-refresh-history - i.fa.fa-refresh - | Refresh - - if isShowingOriginalPositions - .original-positions-content - if isLoading - .original-positions-loading - i.fa.fa-spinner.fa-spin - | Loading original positions... - else - .original-positions-filters - .btn-group.btn-group-sm(role="group") - button.btn.js-filter-type(type="button" class="{{#if isFilterType 'all'}}btn-primary{{else}}btn-outline-secondary{{/if}}" data-filter-type="all") - | All - button.btn.js-filter-type(type="button" class="{{#if isFilterType 'swimlane'}}btn-primary{{else}}btn-outline-secondary{{/if}}" data-filter-type="swimlane") - i.fa.fa-bars - | Swimlanes - button.btn.js-filter-type(type="button" class="{{#if isFilterType 'list'}}btn-primary{{else}}btn-outline-secondary{{/if}}" data-filter-type="list") - i.fa.fa-columns - | Lists - button.btn.js-filter-type(type="button" class="{{#if isFilterType 'card'}}btn-primary{{else}}btn-outline-secondary{{/if}}" data-filter-type="card") - i.fa.fa-sticky-note - | Cards - - .original-positions-list - each getFilteredHistory - .original-position-item - .original-position-item-header - i.fa(class="{{getEntityTypeIcon entityType}}") - span.entity-type {{getEntityTypeLabel entityType}} - span.entity-name {{getEntityDisplayName this}} - span.entity-id ({{entityId}}) - .original-position-item-details - .original-position-description - | {{getEntityOriginalPositionDescription this}} - if originalTitle - .original-title - strong Original title: - | {{originalTitle}} - .original-position-date - small.text-muted Created: {{formatDate createdAt}} - else - .no-original-positions - i.fa.fa-info-circle - | No original position data available for this board. diff --git a/client/components/boards/originalPositionsView.js b/client/components/boards/originalPositionsView.js index 2a3cb2a51..1e73796be 100644 --- a/client/components/boards/originalPositionsView.js +++ b/client/components/boards/originalPositionsView.js @@ -1,78 +1,99 @@ +import { BlazeComponent } from 'meteor/peerlibrary:blaze-components'; import { ReactiveVar } from 'meteor/reactive-var'; import { Meteor } from 'meteor/meteor'; +import { Template } from 'meteor/templating'; +import './originalPositionsView.html'; /** * Component to display original positions for all entities on a board */ +class OriginalPositionsViewComponent extends BlazeComponent { + onCreated() { + super.onCreated(); + this.showOriginalPositions = new ReactiveVar(false); + this.boardHistory = new ReactiveVar([]); + this.isLoading = new ReactiveVar(false); + this.filterType = new ReactiveVar('all'); // 'all', 'swimlane', 'list', 'card' + } -Template.originalPositionsView.onCreated(function () { - this.showOriginalPositions = new ReactiveVar(false); - this.boardHistory = new ReactiveVar([]); - this.isLoading = new ReactiveVar(false); - this.filterType = new ReactiveVar('all'); // 'all', 'swimlane', 'list', 'card' + onRendered() { + super.onRendered(); + this.loadBoardHistory(); + } - const tpl = this; - - this.loadBoardHistory = function () { + loadBoardHistory() { const boardId = Session.get('currentBoard'); if (!boardId) return; - tpl.isLoading.set(true); - + this.isLoading.set(true); + Meteor.call('positionHistory.getBoardHistory', boardId, (error, result) => { - tpl.isLoading.set(false); + this.isLoading.set(false); if (error) { console.error('Error loading board history:', error); - tpl.boardHistory.set([]); + this.boardHistory.set([]); } else { - tpl.boardHistory.set(result); + this.boardHistory.set(result); } }); - }; -}); + } -Template.originalPositionsView.onRendered(function () { - this.loadBoardHistory(); -}); + toggleOriginalPositions() { + this.showOriginalPositions.set(!this.showOriginalPositions.get()); + } -Template.originalPositionsView.helpers({ isShowingOriginalPositions() { - return Template.instance().showOriginalPositions.get(); - }, + return this.showOriginalPositions.get(); + } isLoading() { - return Template.instance().isLoading.get(); - }, + return this.isLoading.get(); + } getBoardHistory() { - return Template.instance().boardHistory.get(); - }, + return this.boardHistory.get(); + } getFilteredHistory() { - const tpl = Template.instance(); - const history = tpl.boardHistory.get(); - const filterType = tpl.filterType.get(); - + const history = this.getBoardHistory(); + const filterType = this.filterType.get(); + if (filterType === 'all') { return history; } - + return history.filter(item => item.entityType === filterType); - }, + } - isFilterType(type) { - return Template.instance().filterType.get() === type; - }, + getSwimlanesHistory() { + return this.getBoardHistory().filter(item => item.entityType === 'swimlane'); + } + + getListsHistory() { + return this.getBoardHistory().filter(item => item.entityType === 'list'); + } + + getCardsHistory() { + return this.getBoardHistory().filter(item => item.entityType === 'card'); + } + + setFilterType(type) { + this.filterType.set(type); + } + + getFilterType() { + return this.filterType.get(); + } getEntityDisplayName(entity) { const position = entity.originalPosition || {}; return position.title || `Entity ${entity.entityId}`; - }, + } getEntityOriginalPositionDescription(entity) { const position = entity.originalPosition || {}; let description = `Position: ${position.sort || 0}`; - + if (entity.entityType === 'list' && entity.originalSwimlaneId) { description += ` in swimlane ${entity.originalSwimlaneId}`; } else if (entity.entityType === 'card') { @@ -83,9 +104,9 @@ Template.originalPositionsView.helpers({ description += ` in list ${entity.originalListId}`; } } - + return description; - }, + } getEntityTypeIcon(entityType) { switch (entityType) { @@ -98,7 +119,7 @@ Template.originalPositionsView.helpers({ default: return 'fa-question'; } - }, + } getEntityTypeLabel(entityType) { switch (entityType) { @@ -111,24 +132,17 @@ Template.originalPositionsView.helpers({ default: return 'Unknown'; } - }, + } formatDate(date) { return new Date(date).toLocaleString(); - }, -}); + } -Template.originalPositionsView.events({ - 'click .js-toggle-original-positions'(evt, tpl) { - tpl.showOriginalPositions.set(!tpl.showOriginalPositions.get()); - }, + refreshHistory() { + this.loadBoardHistory(); + } +} - 'click .js-refresh-history'(evt, tpl) { - tpl.loadBoardHistory(); - }, +OriginalPositionsViewComponent.register('originalPositionsView'); - 'click .js-filter-type'(evt, tpl) { - const type = evt.currentTarget.dataset.filterType; - tpl.filterType.set(type); - }, -}); +export default OriginalPositionsViewComponent; diff --git a/client/components/cards/attachments.css b/client/components/cards/attachments.css index 97039dfe3..becb29160 100644 --- a/client/components/cards/attachments.css +++ b/client/components/cards/attachments.css @@ -55,12 +55,6 @@ flex-direction: row; align-items: center; } -.attachment-actions a { - margin-left: 16px; -} -.attachment-actions a:first-child { - margin-left: 0; -} .add-attachment { display: flex; align-items: center; @@ -112,9 +106,6 @@ color: white; cursor: pointer; font-size: 4em; - position: absolute; - right: 50px; - top: 16px; } /* Upload progress indicators for drag-and-drop uploads */ @@ -250,6 +241,10 @@ .js-card-details.is-dragging-over { border: 2px dashed #007bff !important; background: rgba(0, 123, 255, 0.05) !important; +} + top: 0; + right: 8px; + position: absolute; } .attachment-arrow { font-size: 4em; @@ -258,20 +253,6 @@ align-self: center; margin: 0 20px; } -#prev-attachment { - font-size: 4em; - color: white; - cursor: pointer; - align-self: center; - margin-left: 70px; -} -#next-attachment { - font-size: 4em; - color: white; - cursor: pointer; - align-self: center; - margin-right: 70px; -} #viewer-content { display: flex; justify-content: center; @@ -285,13 +266,6 @@ max-width: 100%; max-height: 100%; } -#video-viewer { - max-width: 100%; - max-height: 100%; -} -#audio-viewer { - max-width: 100%; -} #pdf-viewer { width: 40vw; height: 100%; @@ -326,19 +300,9 @@ } #prev-attachment { left: 0; - position: absolute; - bottom: 2.2em; - font-size: 1.6em; - padding: 16px; - margin-left: 0; } #next-attachment { right: 0; - position: absolute; - bottom: 2.2em; - font-size: 1.6em; - padding: 16px; - margin-right: 0; } #pdf-viewer { width: 100%; @@ -372,3 +336,36 @@ margin-top: 10px; } } + +/* Attachment migration styles */ +.attachment-item.migrating { + position: relative; + opacity: 0.7; +} + +.attachment-migration-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background: rgba(255, 255, 255, 0.9); + display: flex; + flex-direction: column; + align-items: center; + justify-content: center; + z-index: 10; + border-radius: 4px; +} + +.migration-spinner { + font-size: 24px; + color: #007cba; + margin-bottom: 8px; +} + +.migration-text { + font-size: 12px; + color: #666; + text-align: center; +} diff --git a/client/components/cards/attachments.jade b/client/components/cards/attachments.jade index 054c547d9..34a9b2496 100644 --- a/client/components/cards/attachments.jade +++ b/client/components/cards/attachments.jade @@ -34,11 +34,10 @@ template(name="attachmentViewer") #viewer-overlay.hidden #viewer-top-bar span#attachment-name - a#viewer-close - i.fa.fa-times-thin + a#viewer-close ❌ #viewer-container - i.fa.fa-caret-left#prev-attachment + | ◀️ #viewer-content img#image-viewer.hidden video#video-viewer.hidden(controls="true") @@ -46,7 +45,7 @@ template(name="attachmentViewer") object#pdf-viewer.hidden(type="application/pdf") span.pdf-preview-error {{_ 'preview-pdf-not-supported' }} object#txt-viewer.hidden(type="text/plain") - i.fa.fa-caret-right#next-attachment + | ▶️ template(name="attachmentGallery") @@ -54,7 +53,7 @@ template(name="attachmentGallery") if canModifyCard a.attachment-item.add-attachment.js-add-attachment - i.fa.fa-plus + | ➕ each attachments @@ -88,21 +87,22 @@ template(name="attachmentGallery") span.file-size ({{fileSize size}}) .attachment-actions a.js-download(href="{{link}}?download=true", download="{{name}}", title="{{_ 'download'}}") - i.fa.fa-arrow-down + | ⬇️ if currentUser.isBoardMember unless currentUser.isCommentOnly unless currentUser.isWorker a.js-rename(title="{{_ 'rename'}}") - i.fa.fa-pencil-square-o + | ✏️ a.js-confirm-delete(title="{{_ 'delete'}}") - i.fa.fa-trash + | 🗑️ a.js-open-attachment-menu(data-attachment-link="{{link}}", title="{{_ 'attachmentActionsPopup-title'}}") - i.fa.fa-bars + | ☰ + // Migration spinner overlay if isAttachmentMigrating _id .attachment-migration-overlay .migration-spinner - i.fa.fa-cog.fa-spin + | ⚙️ .migration-text {{_ 'migrating-attachment'}} template(name="attachmentActionsPopup") @@ -110,12 +110,16 @@ template(name="attachmentActionsPopup") li if isImage a(class="{{#if isCover}}js-remove-cover{{else}}js-add-cover{{/if}}") - i.fa.fa-picture-o - | {{#if isCover}}{{_ 'remove-cover'}}{{else}}{{_ 'add-cover'}}{{/if}} + | 📖 + | 🖼️ + if isCover + | {{_ 'remove-cover'}} + else + | {{_ 'add-cover'}} if currentUser.isBoardAdmin if isImage a(class="{{#if isBackgroundImage}}js-remove-background-image{{else}}js-add-background-image{{/if}}") - i.fa.fa-picture-o + | 🖼️ if isBackgroundImage | {{_ 'remove-background-image'}} else @@ -123,19 +127,19 @@ template(name="attachmentActionsPopup") if $neq versions.original.storage "fs" a.js-move-storage-fs - i.fa.fa-arrow-right + | ▶️ | {{_ 'attachment-move-storage-fs'}} if $neq versions.original.storage "gridfs" if versions.original.storage a.js-move-storage-gridfs - i.fa.fa-arrow-right + | ▶️ | {{_ 'attachment-move-storage-gridfs'}} if $neq versions.original.storage "s3" if versions.original.storage a.js-move-storage-s3 - i.fa.fa-arrow-right + | ▶️ | {{_ 'attachment-move-storage-s3'}} template(name="attachmentRenamePopup") diff --git a/client/components/cards/attachments.js b/client/components/cards/attachments.js index 3dd13684f..a883877e1 100644 --- a/client/components/cards/attachments.js +++ b/client/components/cards/attachments.js @@ -495,9 +495,9 @@ Template.previewClipboardImagePopup.events({ }, }); -Template.attachmentActionsPopup.helpers({ +BlazeComponent.extendComponent({ isCover() { - const ret = ReactiveCache.getCard(this.meta.cardId).coverId == this._id; + const ret = ReactiveCache.getCard(this.data().meta.cardId).coverId == this.data()._id; return ret; }, isBackgroundImage() { @@ -505,72 +505,78 @@ Template.attachmentActionsPopup.helpers({ //return currentBoard.backgroundImageURL === $(".attachment-thumbnail-img").attr("src"); return false; }, -}); + events() { + return [ + { + 'click .js-add-cover'() { + ReactiveCache.getCard(this.data().meta.cardId).setCover(this.data()._id); + Popup.back(); + }, + 'click .js-remove-cover'() { + ReactiveCache.getCard(this.data().meta.cardId).unsetCover(); + Popup.back(); + }, + 'click .js-add-background-image'() { + const currentBoard = Utils.getCurrentBoard(); + currentBoard.setBackgroundImageURL(attachmentActionsLink); + Utils.setBackgroundImage(attachmentActionsLink); + Popup.back(); + event.preventDefault(); + }, + 'click .js-remove-background-image'() { + const currentBoard = Utils.getCurrentBoard(); + currentBoard.setBackgroundImageURL(""); + Utils.setBackgroundImage(""); + Popup.back(); + Utils.reload(); + event.preventDefault(); + }, + 'click .js-move-storage-fs'() { + Meteor.call('moveAttachmentToStorage', this.data()._id, "fs"); + Popup.back(); + }, + 'click .js-move-storage-gridfs'() { + Meteor.call('moveAttachmentToStorage', this.data()._id, "gridfs"); + Popup.back(); + }, + 'click .js-move-storage-s3'() { + Meteor.call('moveAttachmentToStorage', this.data()._id, "s3"); + Popup.back(); + }, + } + ] + } +}).register('attachmentActionsPopup'); -Template.attachmentActionsPopup.events({ - 'click .js-add-cover'() { - ReactiveCache.getCard(this.meta.cardId).setCover(this._id); - Popup.back(); - }, - 'click .js-remove-cover'() { - ReactiveCache.getCard(this.meta.cardId).unsetCover(); - Popup.back(); - }, - 'click .js-add-background-image'(event) { - const currentBoard = Utils.getCurrentBoard(); - currentBoard.setBackgroundImageURL(attachmentActionsLink); - Utils.setBackgroundImage(attachmentActionsLink); - Popup.back(); - event.preventDefault(); - }, - 'click .js-remove-background-image'(event) { - const currentBoard = Utils.getCurrentBoard(); - currentBoard.setBackgroundImageURL(""); - Utils.setBackgroundImage(""); - Popup.back(); - Utils.reload(); - event.preventDefault(); - }, - 'click .js-move-storage-fs'() { - Meteor.call('moveAttachmentToStorage', this._id, "fs"); - Popup.back(); - }, - 'click .js-move-storage-gridfs'() { - Meteor.call('moveAttachmentToStorage', this._id, "gridfs"); - Popup.back(); - }, - 'click .js-move-storage-s3'() { - Meteor.call('moveAttachmentToStorage', this._id, "s3"); - Popup.back(); - }, -}); - -Template.attachmentRenamePopup.helpers({ +BlazeComponent.extendComponent({ getNameWithoutExtension() { - const ret = this.name.replace(new RegExp("\." + this.extension + "$"), ""); + const ret = this.data().name.replace(new RegExp("\." + this.data().extension + "$"), ""); return ret; }, -}); - -Template.attachmentRenamePopup.events({ - 'keydown input.js-edit-attachment-name'(evt, tpl) { - // enter = save - if (evt.keyCode === 13) { - tpl.find('button[type=submit]').click(); - } - }, - 'click button.js-submit-edit-attachment-name'(event, tpl) { - // save button pressed - event.preventDefault(); - const name = tpl.$('.js-edit-attachment-name')[0] - .value - .trim() + this.extensionWithDot; - if (name === sanitizeText(name)) { - Meteor.call('renameAttachment', this._id, name); - } - Popup.back(); - }, -}); + events() { + return [ + { + 'keydown input.js-edit-attachment-name'(evt) { + // enter = save + if (evt.keyCode === 13) { + this.find('button[type=submit]').click(); + } + }, + 'click button.js-submit-edit-attachment-name'(event) { + // save button pressed + event.preventDefault(); + const name = this.$('.js-edit-attachment-name')[0] + .value + .trim() + this.data().extensionWithDot; + if (name === sanitizeText(name)) { + Meteor.call('renameAttachment', this.data()._id, name); + } + Popup.back(); + }, + } + ] + } +}).register('attachmentRenamePopup'); // Template helpers for attachment migration status Template.registerHelper('attachmentMigrationStatus', function(attachmentId) { diff --git a/client/components/cards/cardCustomFields.jade b/client/components/cards/cardCustomFields.jade index b9922c341..35afa772b 100644 --- a/client/components/cards/cardCustomFields.jade +++ b/client/components/cards/cardCustomFields.jade @@ -6,10 +6,10 @@ template(name="cardCustomFieldsPopup") span.full-name = name if hasCustomField - i.fa.fa-check + | ✅ hr a.quiet-button.full.js-settings - i.fa.fa-cog + | ⚙️ span {{_ 'settings'}} template(name="cardCustomField") @@ -55,11 +55,10 @@ template(name="cardCustomField-number") template(name="cardCustomField-checkbox") .js-checklist-item.checklist-item(class="{{#if data.value }}is-checked{{/if}}") if canModifyCard - span.check-box-unicode - i.fa(class="{{#if data.value}}fa-check-square{{else}}fa-square-o{{/if}}") + .check-box-container + .check-box.materialCheckBox(class="{{#if data.value }}is-checked{{/if}}") else - span.check-box-unicode - i.fa(class="{{#if data.value}}fa-check-square{{else}}fa-square-o{{/if}}") + .materialCheckBox(class="{{#if data.value }}is-checked{{/if}}") template(name="cardCustomField-currency") if canModifyCard @@ -99,21 +98,6 @@ template(name="cardCustomField-date") b | {{showWeek}} -template(name="cardCustomField-datePopup") - .datepicker-container - form.edit-date - .fields - .left - label(for="date") {{_ 'date'}} - input.js-date-field#date(type="date" name="date" value=showDate autofocus) - .right - label(for="time") {{_ 'time'}} - input.js-time-field#time(type="time" name="time" value=showTime) - if error.get - .warning {{_ error.get}} - button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}} - button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}} - template(name="cardCustomField-dropdown") if canModifyCard +inlinedForm(classNames="js-card-customfield-dropdown") diff --git a/client/components/cards/cardCustomFields.js b/client/components/cards/cardCustomFields.js index 297661999..82c025503 100644 --- a/client/components/cards/cardCustomFields.js +++ b/client/components/cards/cardCustomFields.js @@ -1,39 +1,33 @@ import { TAPi18n } from '/imports/i18n'; -import { - setupDatePicker, - datePickerRendered, - datePickerHelpers, - datePickerEvents, -} from '/client/lib/datepicker'; +import { DatePicker } from '/client/lib/datepicker'; import { ReactiveCache } from '/imports/reactiveCache'; -import { - formatDateTime, - formatDate, +import { + formatDateTime, + formatDate, formatDateByUserPreference, - formatTime, - getISOWeek, - isValidDate, - isBefore, - isAfter, - isSame, - add, - subtract, - startOf, - endOf, - format, - parseDate, - now, - createDate, - fromNow, - calendar + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar } from '/imports/lib/dateUtils'; +import Cards from '/models/cards'; import { CustomFieldStringTemplate } from '/client/lib/customFields' -import { getCurrentCardFromContext } from '/client/lib/currentCard'; Template.cardCustomFieldsPopup.helpers({ hasCustomField() { - const card = getCurrentCardFromContext(); - if (!card) return false; + const card = Utils.getCurrentCard(); const customFieldId = this._id; return card.customFieldIndex(customFieldId) > -1; }, @@ -41,8 +35,7 @@ Template.cardCustomFieldsPopup.helpers({ Template.cardCustomFieldsPopup.events({ 'click .js-select-field'(event) { - const card = getCurrentCardFromContext(); - if (!card) return; + const card = Utils.getCurrentCard(); const customFieldId = this._id; card.toggleCustomField(customFieldId); event.preventDefault(); @@ -55,280 +48,304 @@ Template.cardCustomFieldsPopup.events({ }); // cardCustomField -Template.cardCustomField.helpers({ +const CardCustomField = BlazeComponent.extendComponent({ getTemplate() { - return `cardCustomField-${this.definition.type}`; + return `cardCustomField-${this.data().definition.type}`; + }, + + onCreated() { + const self = this; + self.card = Utils.getCurrentCard(); + self.customFieldId = this.data()._id; }, }); - -Template.cardCustomField.onCreated(function () { - this.card = getCurrentCardFromContext(); - this.customFieldId = Template.currentData()._id; -}); +CardCustomField.register('cardCustomField'); // cardCustomField-text -Template['cardCustomField-text'].onCreated(function () { - this.card = getCurrentCardFromContext(); - this.customFieldId = Template.currentData()._id; -}); +(class extends CardCustomField { + onCreated() { + super.onCreated(); + } -Template['cardCustomField-text'].events({ - 'submit .js-card-customfield-text'(event, tpl) { - event.preventDefault(); - const value = tpl.currentComponent ? tpl.currentComponent().getValue() : tpl.$('textarea').val(); - tpl.card.setCustomField(tpl.customFieldId, value); - }, -}); + events() { + return [ + { + 'submit .js-card-customfield-text'(event) { + event.preventDefault(); + const value = this.currentComponent().getValue(); + this.card.setCustomField(this.customFieldId, value); + }, + }, + ]; + } +}.register('cardCustomField-text')); // cardCustomField-number -Template['cardCustomField-number'].onCreated(function () { - this.card = getCurrentCardFromContext(); - this.customFieldId = Template.currentData()._id; -}); +(class extends CardCustomField { + onCreated() { + super.onCreated(); + } -Template['cardCustomField-number'].events({ - 'submit .js-card-customfield-number'(event, tpl) { - event.preventDefault(); - const value = parseInt(tpl.find('input').value, 10); - tpl.card.setCustomField(tpl.customFieldId, value); - }, -}); + events() { + return [ + { + 'submit .js-card-customfield-number'(event) { + event.preventDefault(); + const value = parseInt(this.find('input').value, 10); + this.card.setCustomField(this.customFieldId, value); + }, + }, + ]; + } +}.register('cardCustomField-number')); // cardCustomField-checkbox -Template['cardCustomField-checkbox'].onCreated(function () { - this.card = getCurrentCardFromContext(); - this.customFieldId = Template.currentData()._id; -}); +(class extends CardCustomField { + onCreated() { + super.onCreated(); + } -Template['cardCustomField-checkbox'].events({ - 'click .js-checklist-item .check-box-unicode'(event, tpl) { - tpl.card.setCustomField(tpl.customFieldId, !Template.currentData().value); - }, - 'click .js-checklist-item .check-box-container'(event, tpl) { - tpl.card.setCustomField(tpl.customFieldId, !Template.currentData().value); - }, -}); + toggleItem() { + this.card.setCustomField(this.customFieldId, !this.data().value); + } + + events() { + return [ + { + 'click .js-checklist-item .check-box-container': this.toggleItem, + }, + ]; + } +}.register('cardCustomField-checkbox')); // cardCustomField-currency -Template['cardCustomField-currency'].onCreated(function () { - this.card = getCurrentCardFromContext(); - this.customFieldId = Template.currentData()._id; - this.currencyCode = Template.currentData().definition.settings.currencyCode; -}); +(class extends CardCustomField { + onCreated() { + super.onCreated(); + + this.currencyCode = this.data().definition.settings.currencyCode; + } -Template['cardCustomField-currency'].helpers({ formattedValue() { const locale = TAPi18n.getLanguage(); - const tpl = Template.instance(); + return new Intl.NumberFormat(locale, { style: 'currency', - currency: tpl.currencyCode, - }).format(this.value); - }, -}); + currency: this.currencyCode, + }).format(this.data().value); + } -Template['cardCustomField-currency'].events({ - 'submit .js-card-customfield-currency'(event, tpl) { - event.preventDefault(); - // To allow input separated by comma, the comma is replaced by a period. - const value = Number(tpl.find('input').value.replace(/,/i, '.'), 10); - tpl.card.setCustomField(tpl.customFieldId, value); - }, -}); + events() { + return [ + { + 'submit .js-card-customfield-currency'(event) { + event.preventDefault(); + // To allow input separated by comma, the comma is replaced by a period. + const value = Number(this.find('input').value.replace(/,/i, '.'), 10); + this.card.setCustomField(this.customFieldId, value); + }, + }, + ]; + } +}.register('cardCustomField-currency')); // cardCustomField-date -Template['cardCustomField-date'].onCreated(function () { - this.card = getCurrentCardFromContext(); - this.customFieldId = Template.currentData()._id; - const self = this; - self.date = ReactiveVar(); - self.now = ReactiveVar(now()); - window.setInterval(() => { - self.now.set(now()); - }, 60000); +(class extends CardCustomField { + onCreated() { + super.onCreated(); + const self = this; + self.date = ReactiveVar(); + self.now = ReactiveVar(now()); + window.setInterval(() => { + self.now.set(now()); + }, 60000); - self.autorun(() => { - self.date.set(new Date(Template.currentData().value)); - }); -}); + self.autorun(() => { + self.date.set(new Date(self.data().value)); + }); + } -Template['cardCustomField-date'].helpers({ showWeek() { - return getISOWeek(Template.instance().date.get()).toString(); - }, + return getISOWeek(this.date.get()).toString(); + } + showWeekOfYear() { const user = ReactiveCache.getCurrentUser(); if (!user) { + // For non-logged-in users, week of year is not shown return false; } return user.isShowWeekOfYear(); - }, + } + showDate() { const currentUser = ReactiveCache.getCurrentUser(); const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - return formatDateByUserPreference(Template.instance().date.get(), dateFormat, true); - }, + return formatDateByUserPreference(this.date.get(), dateFormat, true); + } + showISODate() { - return Template.instance().date.get().toISOString(); - }, + return this.date.get().toISOString(); + } + classes() { - const tpl = Template.instance(); if ( - isBefore(tpl.date.get(), tpl.now.get(), 'minute') && - isBefore(tpl.now.get(), this.value, 'minute') + isBefore(this.date.get(), this.now.get(), 'minute') && + isBefore(this.now.get(), this.data().value, 'minute') ) { return 'current'; } return ''; - }, - showTitle() { - return `${TAPi18n.__('card-start-on')} ${Template.instance().date.get().toLocaleString()}`; - }, -}); + } -Template['cardCustomField-date'].events({ - 'click .js-edit-date': Popup.open('cardCustomField-date'), -}); + showTitle() { + return `${TAPi18n.__('card-start-on')} ${this.date.get().toLocaleString()}`; + } + + events() { + return [ + { + 'click .js-edit-date': Popup.open('cardCustomField-date'), + }, + ]; + } +}.register('cardCustomField-date')); // cardCustomField-datePopup -Template['cardCustomField-datePopup'].onCreated(function () { - const data = Template.currentData(); - setupDatePicker(this, { - initialDate: data.value ? data.value : undefined, - }); - // Override card and store customFieldId for store/delete callbacks - this.datePicker.card = getCurrentCardFromContext(); - this.customFieldId = data._id; -}); +(class extends DatePicker { + onCreated() { + super.onCreated(); + const self = this; + self.card = Utils.getCurrentCard(); + self.customFieldId = this.data()._id; + this.data().value && this.date.set(new Date(this.data().value)); + } -Template['cardCustomField-datePopup'].onRendered(function () { - datePickerRendered(this); -}); + _storeDate(date) { + this.card.setCustomField(this.customFieldId, date); + } -Template['cardCustomField-datePopup'].helpers(datePickerHelpers()); - -Template['cardCustomField-datePopup'].events(datePickerEvents({ - storeDate(date) { - this.datePicker.card.setCustomField(this.customFieldId, date); - }, - deleteDate() { - this.datePicker.card.setCustomField(this.customFieldId, ''); - }, -})); + _deleteDate() { + this.card.setCustomField(this.customFieldId, ''); + } +}.register('cardCustomField-datePopup')); // cardCustomField-dropdown -Template['cardCustomField-dropdown'].onCreated(function () { - this.card = getCurrentCardFromContext(); - this.customFieldId = Template.currentData()._id; - this._items = Template.currentData().definition.settings.dropdownItems; - this.items = this._items.slice(0); - this.items.unshift({ - _id: '', - name: TAPi18n.__('custom-field-dropdown-none'), - }); -}); +(class extends CardCustomField { + onCreated() { + super.onCreated(); + this._items = this.data().definition.settings.dropdownItems; + this.items = this._items.slice(0); + this.items.unshift({ + _id: '', + name: TAPi18n.__('custom-field-dropdown-none'), + }); + } -Template['cardCustomField-dropdown'].helpers({ - items() { - return Template.instance().items; - }, selectedItem() { - const tpl = Template.instance(); - const selected = tpl._items.find(item => { - return item._id === this.value; + const selected = this._items.find(item => { + return item._id === this.data().value; }); return selected ? selected.name : TAPi18n.__('custom-field-dropdown-unknown'); - }, -}); + } -Template['cardCustomField-dropdown'].events({ - 'submit .js-card-customfield-dropdown'(event, tpl) { - event.preventDefault(); - const value = tpl.find('select').value; - tpl.card.setCustomField(tpl.customFieldId, value); - }, -}); + events() { + return [ + { + 'submit .js-card-customfield-dropdown'(event) { + event.preventDefault(); + const value = this.find('select').value; + this.card.setCustomField(this.customFieldId, value); + }, + }, + ]; + } +}.register('cardCustomField-dropdown')); // cardCustomField-stringtemplate -Template['cardCustomField-stringtemplate'].onCreated(function () { - this.card = getCurrentCardFromContext(); - this.customFieldId = Template.currentData()._id; - this.customField = new CustomFieldStringTemplate(Template.currentData().definition); - this.stringtemplateItems = new ReactiveVar(Template.currentData().value ?? []); -}); +class CardCustomFieldStringTemplate extends CardCustomField { + onCreated() { + super.onCreated(); + + this.customField = new CustomFieldStringTemplate(this.data().definition); + + this.stringtemplateItems = new ReactiveVar(this.data().value ?? []); + } -Template['cardCustomField-stringtemplate'].helpers({ formattedValue() { - const tpl = Template.instance(); - const ret = tpl.customField.getFormattedValue(this.value); + const ret = this.customField.getFormattedValue(this.data().value); return ret; - }, - stringtemplateItems() { - return Template.instance().stringtemplateItems.get(); - }, -}); + } -Template['cardCustomField-stringtemplate'].events({ - 'submit .js-card-customfield-stringtemplate'(event, tpl) { - event.preventDefault(); - const items = tpl.stringtemplateItems.get(); - tpl.card.setCustomField(tpl.customFieldId, items); - }, + getItems() { + return Array.from(this.findAll('input')) + .map(input => input.value) + .filter(value => !!value.trim()); + } - 'keydown .js-card-customfield-stringtemplate-item'(event, tpl) { - if (event.keyCode === 13) { - event.preventDefault(); + events() { + return [ + { + 'submit .js-card-customfield-stringtemplate'(event) { + event.preventDefault(); + const items = this.stringtemplateItems.get(); + this.card.setCustomField(this.customFieldId, items); + }, - if (event.target.value.trim() || event.metaKey || event.ctrlKey) { - const inputLast = tpl.find('input.last'); + 'keydown .js-card-customfield-stringtemplate-item'(event) { + if (event.keyCode === 13) { + event.preventDefault(); - let items = Array.from(tpl.findAll('input')) - .map(input => input.value) - .filter(value => !!value.trim()); + if (event.target.value.trim() || event.metaKey || event.ctrlKey) { + const inputLast = this.find('input.last'); - if (event.target === inputLast) { - inputLast.value = ''; - } else if (event.target.nextSibling === inputLast) { - inputLast.focus(); - } else { - event.target.blur(); + let items = this.getItems(); - const idx = Array.from(tpl.findAll('input')).indexOf( - event.target, - ); - items.splice(idx + 1, 0, ''); + if (event.target === inputLast) { + inputLast.value = ''; + } else if (event.target.nextSibling === inputLast) { + inputLast.focus(); + } else { + event.target.blur(); - Tracker.afterFlush(() => { - const element = tpl.findAll('input')[idx + 1]; - element.focus(); - element.value = ''; - }); - } + const idx = Array.from(this.findAll('input')).indexOf( + event.target, + ); + items.splice(idx + 1, 0, ''); - tpl.stringtemplateItems.set(items); - } - if (event.metaKey || event.ctrlKey) { - tpl.find('button[type=submit]').click(); - } - } - }, + Tracker.afterFlush(() => { + const element = this.findAll('input')[idx + 1]; + element.focus(); + element.value = ''; + }); + } - 'blur .js-card-customfield-stringtemplate-item'(event, tpl) { - if ( - !event.target.value.trim() || - event.target === tpl.find('input.last') - ) { - const items = Array.from(tpl.findAll('input')) - .map(input => input.value) - .filter(value => !!value.trim()); - tpl.stringtemplateItems.set(items); - tpl.find('input.last').value = ''; - } - }, + this.stringtemplateItems.set(items); + } + if (event.metaKey || event.ctrlKey) { + this.find('button[type=submit]').click(); + } + } + }, - 'click .js-close-inlined-form'(event, tpl) { - tpl.stringtemplateItems.set(Template.currentData().value ?? []); - }, -}); + 'blur .js-card-customfield-stringtemplate-item'(event) { + if ( + !event.target.value.trim() || + event.target === this.find('input.last') + ) { + const items = this.getItems(); + this.stringtemplateItems.set(items); + this.find('input.last').value = ''; + } + }, + + 'click .js-close-inlined-form'(event) { + this.stringtemplateItems.set(this.data().value ?? []); + }, + }, + ]; + } +} +CardCustomFieldStringTemplate.register('cardCustomField-stringtemplate'); diff --git a/client/components/cards/cardDate.jade b/client/components/cards/cardDate.jade index 56f4b9eb6..e387112bc 100644 --- a/client/components/cards/cardDate.jade +++ b/client/components/cards/cardDate.jade @@ -14,70 +14,6 @@ template(name="dateBadge") b | {{showWeek}} -template(name="cardReceivedDate") - if canModifyCard - a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") - time(datetime="{{showISODate}}") - | {{showDate}} - if showWeekOfYear - b - | {{showWeek}} - else - a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") - time(datetime="{{showISODate}}") - | {{showDate}} - if showWeekOfYear - b - | {{showWeek}} - -template(name="cardStartDate") - if canModifyCard - a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") - time(datetime="{{showISODate}}") - | {{showDate}} - if showWeekOfYear - b - | {{showWeek}} - else - a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") - time(datetime="{{showISODate}}") - | {{showDate}} - if showWeekOfYear - b - | {{showWeek}} - -template(name="cardDueDate") - if canModifyCard - a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") - time(datetime="{{showISODate}}") - | {{showDate}} - if showWeekOfYear - b - | {{showWeek}} - else - a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") - time(datetime="{{showISODate}}") - | {{showDate}} - if showWeekOfYear - b - | {{showWeek}} - -template(name="cardEndDate") - if canModifyCard - a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") - time(datetime="{{showISODate}}") - | {{showDate}} - if showWeekOfYear - b - | {{showWeek}} - else - a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") - time(datetime="{{showISODate}}") - | {{showDate}} - if showWeekOfYear - b - | {{showWeek}} - template(name="dateCustomField") a(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") time(datetime="{{showISODate}}") @@ -86,46 +22,6 @@ template(name="dateCustomField") b | {{showWeek}} -template(name="cardCustomFieldDate") - a(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") - time(datetime="{{showISODate}}") - | {{showDate}} - if showWeekOfYear - b - | {{showWeek}} - -template(name="voteEndDate") - if canModifyCard - a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") - time(datetime="{{showISODate}}") - | {{showDate}} - if showWeekOfYear - b - | {{showWeek}} - else - a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") - time(datetime="{{showISODate}}") - | {{showDate}} - if showWeekOfYear - b - | {{showWeek}} - -template(name="pokerEndDate") - if canModifyCard - a.js-edit-date.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") - time(datetime="{{showISODate}}") - | {{showDate}} - if showWeekOfYear - b - | {{showWeek}} - else - a.card-date(title="{{showTitle}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") - time(datetime="{{showISODate}}") - | {{showDate}} - if showWeekOfYear - b - | {{showWeek}} - template(name="minicardReceivedDate") if canModifyCard a.js-edit-date.card-date.received-date(title="{{_ 'card-received'}} {{_ 'predicate-week'}} {{#if showWeekOfYear}}{{showWeek}}{{/if}}" class="{{classes}}") @@ -199,91 +95,37 @@ template(name="minicardCustomFieldDate") | {{showWeek}} template(name="editCardReceivedDatePopup") - .datepicker-container - form.edit-date - .fields - .left - label(for="date") {{_ 'date'}} - input.js-date-field#date(type="date" name="date" value=showDate autofocus) - .right - label(for="time") {{_ 'time'}} - input.js-time-field#time(type="time" name="time" value=showTime) - if error.get - .warning {{_ error.get}} - button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}} - button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}} + form.edit-card-received-date + .datepicker + .clear-date + a.js-clear-date {{_ 'clear'}} + .datepicker-actions + button.primary.wide.left(type="submit") {{_ 'save'}} + button.js-delete-date.negate.wide.right {{_ 'delete'}} template(name="editCardStartDatePopup") - .datepicker-container - form.edit-date - .fields - .left - label(for="date") {{_ 'date'}} - input.js-date-field#date(type="date" name="date" value=showDate autofocus) - .right - label(for="time") {{_ 'time'}} - input.js-time-field#time(type="time" name="time" value=showTime) - if error.get - .warning {{_ error.get}} - button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}} - button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}} + form.edit-card-start-date + .datepicker + .clear-date + a.js-clear-date {{_ 'clear'}} + .datepicker-actions + button.primary.wide.left(type="submit") {{_ 'save'}} + button.js-delete-date.negate.wide.right {{_ 'delete'}} template(name="editCardDueDatePopup") - .datepicker-container - form.edit-date - .fields - .left - label(for="date") {{_ 'date'}} - input.js-date-field#date(type="date" name="date" value=showDate autofocus) - .right - label(for="time") {{_ 'time'}} - input.js-time-field#time(type="time" name="time" value=showTime) - if error.get - .warning {{_ error.get}} - button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}} - button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}} + form.edit-card-due-date + .datepicker + .clear-date + a.js-clear-date {{_ 'clear'}} + .datepicker-actions + button.primary.wide.left(type="submit") {{_ 'save'}} + button.js-delete-date.negate.wide.right {{_ 'delete'}} template(name="editCardEndDatePopup") - .datepicker-container - form.edit-date - .fields - .left - label(for="date") {{_ 'date'}} - input.js-date-field#date(type="date" name="date" value=showDate autofocus) - .right - label(for="time") {{_ 'time'}} - input.js-time-field#time(type="time" name="time" value=showTime) - if error.get - .warning {{_ error.get}} - button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}} - button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}} - -template(name="editVoteEndDatePopup") - .datepicker-container - form.edit-date - .fields - .left - label(for="date") {{_ 'date'}} - input.js-date-field#date(type="date" name="date" value=showDate autofocus) - .right - label(for="time") {{_ 'time'}} - input.js-time-field#time(type="time" name="time" value=showTime) - if error.get - .warning {{_ error.get}} - button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}} - button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}} - -template(name="editPokerEndDatePopup") - .datepicker-container - form.edit-date - .fields - .left - label(for="date") {{_ 'date'}} - input.js-date-field#date(type="date" name="date" value=showDate autofocus) - .right - label(for="time") {{_ 'time'}} - input.js-time-field#time(type="time" name="time" value=showTime) - if error.get - .warning {{_ error.get}} - button.primary.wide.left.js-submit-date(type="submit") {{_ 'save'}} - button.js-delete-date.negate.wide.right.js-delete-date {{_ 'delete'}} + form.edit-card-end-date + .datepicker + .clear-date + a.js-clear-date {{_ 'clear'}} + .datepicker-actions + button.primary.wide.left(type="submit") {{_ 'save'}} + button.js-delete-date.negate.wide.right {{_ 'delete'}} diff --git a/client/components/cards/cardDate.js b/client/components/cards/cardDate.js index b8bce20eb..bf40538db 100644 --- a/client/components/cards/cardDate.js +++ b/client/components/cards/cardDate.js @@ -1,187 +1,165 @@ import { TAPi18n } from '/imports/i18n'; -import { ReactiveCache } from '/imports/reactiveCache'; -import { - setupDatePicker, - datePickerRendered, - datePickerHelpers, - datePickerEvents, -} from '/client/lib/datepicker'; -import { - formatDateTime, - formatDate, +import { DatePicker } from '/client/lib/datepicker'; +import { + formatDateTime, + formatDate, formatDateByUserPreference, - formatTime, - getISOWeek, - isValidDate, - isBefore, - isAfter, - isSame, - add, - subtract, - startOf, - endOf, - format, - parseDate, - now, - createDate, - fromNow, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, calendar, diff } from '/imports/lib/dateUtils'; -// --- DatePicker popups (edit date forms) --- - // editCardReceivedDatePopup -Template.editCardReceivedDatePopup.onCreated(function () { - const card = Template.currentData(); - setupDatePicker(this, { - defaultTime: formatDateTime(now()), - initialDate: card.getReceived() ? card.getReceived() : undefined, - }); -}); +(class extends DatePicker { + onCreated() { + super.onCreated(formatDateTime(now())); + this.data().getReceived() && + this.date.set(new Date(this.data().getReceived())); + } -Template.editCardReceivedDatePopup.onRendered(function () { - datePickerRendered(this); -}); + _storeDate(date) { + this.card.setReceived(formatDateTime(date)); + } -Template.editCardReceivedDatePopup.helpers(datePickerHelpers()); - -Template.editCardReceivedDatePopup.events(datePickerEvents({ - storeDate(date) { - this.datePicker.card.setReceived(formatDateTime(date)); - }, - deleteDate() { - this.datePicker.card.unsetReceived(); - }, -})); + _deleteDate() { + this.card.unsetReceived(); + } +}.register('editCardReceivedDatePopup')); // editCardStartDatePopup -Template.editCardStartDatePopup.onCreated(function () { - const card = Template.currentData(); - setupDatePicker(this, { - defaultTime: formatDateTime(now()), - initialDate: card.getStart() ? card.getStart() : undefined, - }); -}); +(class extends DatePicker { + onCreated() { + super.onCreated(formatDateTime(now())); + this.data().getStart() && this.date.set(new Date(this.data().getStart())); + } -Template.editCardStartDatePopup.onRendered(function () { - datePickerRendered(this); -}); + onRendered() { + super.onRendered(); + // DatePicker base class handles initialization with native HTML inputs + } -Template.editCardStartDatePopup.helpers(datePickerHelpers()); + _storeDate(date) { + this.card.setStart(formatDateTime(date)); + } -Template.editCardStartDatePopup.events(datePickerEvents({ - storeDate(date) { - this.datePicker.card.setStart(formatDateTime(date)); - }, - deleteDate() { - this.datePicker.card.unsetStart(); - }, -})); + _deleteDate() { + this.card.unsetStart(); + } +}.register('editCardStartDatePopup')); // editCardDueDatePopup -Template.editCardDueDatePopup.onCreated(function () { - const card = Template.currentData(); - setupDatePicker(this, { - defaultTime: '1970-01-01 17:00:00', - initialDate: card.getDue() ? card.getDue() : undefined, - }); -}); +(class extends DatePicker { + onCreated() { + super.onCreated('1970-01-01 17:00:00'); + this.data().getDue() && this.date.set(new Date(this.data().getDue())); + } -Template.editCardDueDatePopup.onRendered(function () { - datePickerRendered(this); -}); + onRendered() { + super.onRendered(); + // DatePicker base class handles initialization with native HTML inputs + } -Template.editCardDueDatePopup.helpers(datePickerHelpers()); + _storeDate(date) { + this.card.setDue(formatDateTime(date)); + } -Template.editCardDueDatePopup.events(datePickerEvents({ - storeDate(date) { - this.datePicker.card.setDue(formatDateTime(date)); - }, - deleteDate() { - this.datePicker.card.unsetDue(); - }, -})); + _deleteDate() { + this.card.unsetDue(); + } +}.register('editCardDueDatePopup')); // editCardEndDatePopup -Template.editCardEndDatePopup.onCreated(function () { - const card = Template.currentData(); - setupDatePicker(this, { - defaultTime: formatDateTime(now()), - initialDate: card.getEnd() ? card.getEnd() : undefined, - }); -}); +(class extends DatePicker { + onCreated() { + super.onCreated(formatDateTime(now())); + this.data().getEnd() && this.date.set(new Date(this.data().getEnd())); + } -Template.editCardEndDatePopup.onRendered(function () { - datePickerRendered(this); -}); + onRendered() { + super.onRendered(); + // DatePicker base class handles initialization with native HTML inputs + } -Template.editCardEndDatePopup.helpers(datePickerHelpers()); + _storeDate(date) { + this.card.setEnd(formatDateTime(date)); + } -Template.editCardEndDatePopup.events(datePickerEvents({ - storeDate(date) { - this.datePicker.card.setEnd(formatDateTime(date)); + _deleteDate() { + this.card.unsetEnd(); + } +}.register('editCardEndDatePopup')); + +// Display received, start, due & end dates +const CardDate = BlazeComponent.extendComponent({ + template() { + return 'dateBadge'; }, - deleteDate() { - this.datePicker.card.unsetEnd(); + + onCreated() { + const self = this; + self.date = ReactiveVar(); + self.now = ReactiveVar(now()); + window.setInterval(() => { + self.now.set(now()); + }, 60000); }, -})); -// --- Card date badge display helpers --- + showWeek() { + return getISOWeek(this.date.get()).toString(); + }, -// Shared onCreated logic for card date badge templates -function cardDateOnCreated(tpl) { - tpl.date = new ReactiveVar(); - tpl.now = new ReactiveVar(now()); - window.setInterval(() => { - tpl.now.set(now()); - }, 60000); -} + showWeekOfYear() { + const user = ReactiveCache.getCurrentUser(); + if (!user) { + // For non-logged-in users, week of year is not shown + return false; + } + return user.isShowWeekOfYear(); + }, -// Shared helpers for card date badge templates -function cardDateHelpers(extraHelpers) { - const base = { - showWeek() { - return getISOWeek(Template.instance().date.get()).toString(); - }, - showWeekOfYear() { - const user = ReactiveCache.getCurrentUser(); - if (!user) { - return false; - } - return user.isShowWeekOfYear(); - }, - showDate() { - const currentUser = ReactiveCache.getCurrentUser(); - const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - return formatDateByUserPreference(Template.instance().date.get(), dateFormat, true); - }, - showISODate() { - return Template.instance().date.get().toISOString(); - }, - }; - return Object.assign(base, extraHelpers); -} + showDate() { + const currentUser = ReactiveCache.getCurrentUser(); + const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; + return formatDateByUserPreference(this.date.get(), dateFormat, true); + }, -// cardReceivedDate -Template.cardReceivedDate.onCreated(function () { - cardDateOnCreated(this); - const self = this; - self.autorun(() => { - self.date.set(new Date(Template.currentData().getReceived())); - }); + showISODate() { + return this.date.get().toISOString(); + }, }); -Template.cardReceivedDate.helpers(cardDateHelpers({ +class CardReceivedDate extends CardDate { + onCreated() { + super.onCreated(); + const self = this; + self.autorun(() => { + self.date.set(new Date(self.data().getReceived())); + }); + } + classes() { - const tpl = Template.instance(); let classes = 'received-date '; - const data = Template.currentData(); - const dueAt = data.getDue(); - const endAt = data.getEnd(); - const startAt = data.getStart(); - const theDate = tpl.date.get(); - + const dueAt = this.data().getDue(); + const endAt = this.data().getEnd(); + const startAt = this.data().getStart(); + const theDate = this.date.get(); + const now = this.now.get(); + + // Received date logic: if received date is after start, due, or end dates, it's overdue if ( (startAt && isAfter(theDate, startAt)) || (endAt && isAfter(theDate, endAt)) || @@ -192,453 +170,332 @@ Template.cardReceivedDate.helpers(cardDateHelpers({ classes += 'not-due'; } return classes; - }, + } + showTitle() { - const tpl = Template.instance(); const currentUser = ReactiveCache.getCurrentUser(); const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - const formattedDate = formatDateByUserPreference(tpl.date.get(), dateFormat, true); + const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true); return `${TAPi18n.__('card-received-on')} ${formattedDate}`; - }, -})); + } -Template.cardReceivedDate.events({ - 'click .js-edit-date': Popup.open('editCardReceivedDate'), -}); + events() { + return super.events().concat({ + 'click .js-edit-date': Popup.open('editCardReceivedDate'), + }); + } +} +CardReceivedDate.register('cardReceivedDate'); -// cardStartDate -Template.cardStartDate.onCreated(function () { - cardDateOnCreated(this); - const self = this; - self.autorun(() => { - self.date.set(new Date(Template.currentData().getStart())); - }); -}); +class CardStartDate extends CardDate { + onCreated() { + super.onCreated(); + const self = this; + self.autorun(() => { + self.date.set(new Date(self.data().getStart())); + }); + } -Template.cardStartDate.helpers(cardDateHelpers({ classes() { - const tpl = Template.instance(); let classes = 'start-date '; - const data = Template.currentData(); - const dueAt = data.getDue(); - const endAt = data.getEnd(); - const theDate = tpl.date.get(); - const nowVal = tpl.now.get(); - + const dueAt = this.data().getDue(); + const endAt = this.data().getEnd(); + const theDate = this.date.get(); + const now = this.now.get(); + + // Start date logic: if start date is after due or end dates, it's overdue if ((endAt && isAfter(theDate, endAt)) || (dueAt && isAfter(theDate, dueAt))) { classes += 'overdue'; - } else if (isAfter(theDate, nowVal)) { + } else if (isAfter(theDate, now)) { + // Start date is in the future - not due yet classes += 'not-due'; } else { + // Start date is today or in the past - current/active classes += 'current'; } return classes; - }, + } + showTitle() { - const tpl = Template.instance(); const currentUser = ReactiveCache.getCurrentUser(); const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - const formattedDate = formatDateByUserPreference(tpl.date.get(), dateFormat, true); + const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true); return `${TAPi18n.__('card-start-on')} ${formattedDate}`; - }, -})); + } -Template.cardStartDate.events({ - 'click .js-edit-date': Popup.open('editCardStartDate'), -}); + events() { + return super.events().concat({ + 'click .js-edit-date': Popup.open('editCardStartDate'), + }); + } +} +CardStartDate.register('cardStartDate'); -// cardDueDate -Template.cardDueDate.onCreated(function () { - cardDateOnCreated(this); - const self = this; - self.autorun(() => { - self.date.set(new Date(Template.currentData().getDue())); - }); -}); +class CardDueDate extends CardDate { + onCreated() { + super.onCreated(); + const self = this; + self.autorun(() => { + self.date.set(new Date(self.data().getDue())); + }); + } -Template.cardDueDate.helpers(cardDateHelpers({ classes() { - const tpl = Template.instance(); let classes = 'due-date '; - const data = Template.currentData(); - const endAt = data.getEnd(); - const theDate = tpl.date.get(); - const nowVal = tpl.now.get(); - + const endAt = this.data().getEnd(); + const theDate = this.date.get(); + const now = this.now.get(); + + // If there's an end date and it's before the due date, task is completed early if (endAt && isBefore(endAt, theDate)) { classes += 'completed-early'; - } else if (endAt) { + } + // If there's an end date, don't show due date status since task is completed + else if (endAt) { classes += 'completed'; - } else { - const daysDiff = diff(theDate, nowVal, 'days'); - + } + // Due date logic based on current time + else { + const daysDiff = diff(theDate, now, 'days'); + if (daysDiff < 0) { + // Due date is in the past - overdue classes += 'overdue'; } else if (daysDiff <= 1) { + // Due today or tomorrow - due soon classes += 'due-soon'; } else { + // Due date is more than 1 day away - not due yet classes += 'not-due'; } } - + return classes; - }, + } + showTitle() { - const tpl = Template.instance(); const currentUser = ReactiveCache.getCurrentUser(); const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - const formattedDate = formatDateByUserPreference(tpl.date.get(), dateFormat, true); + const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true); return `${TAPi18n.__('card-due-on')} ${formattedDate}`; - }, -})); + } -Template.cardDueDate.events({ - 'click .js-edit-date': Popup.open('editCardDueDate'), -}); + events() { + return super.events().concat({ + 'click .js-edit-date': Popup.open('editCardDueDate'), + }); + } +} +CardDueDate.register('cardDueDate'); -// cardEndDate -Template.cardEndDate.onCreated(function () { - cardDateOnCreated(this); - const self = this; - self.autorun(() => { - self.date.set(new Date(Template.currentData().getEnd())); - }); -}); +class CardEndDate extends CardDate { + onCreated() { + super.onCreated(); + const self = this; + self.autorun(() => { + self.date.set(new Date(self.data().getEnd())); + }); + } -Template.cardEndDate.helpers(cardDateHelpers({ classes() { - const tpl = Template.instance(); let classes = 'end-date '; - const data = Template.currentData(); - const dueAt = data.getDue(); - const theDate = tpl.date.get(); - + const dueAt = this.data().getDue(); + const theDate = this.date.get(); + if (!dueAt) { + // No due date set - just show as completed classes += 'completed'; } else if (isBefore(theDate, dueAt)) { + // End date is before due date - completed early classes += 'completed-early'; } else if (isAfter(theDate, dueAt)) { + // End date is after due date - completed late classes += 'completed-late'; } else { + // End date equals due date - completed on time classes += 'completed-on-time'; } return classes; - }, + } + showTitle() { - const tpl = Template.instance(); - return `${TAPi18n.__('card-end-on')} ${format(tpl.date.get(), 'LLLL')}`; - }, -})); + return `${TAPi18n.__('card-end-on')} ${format(this.date.get(), 'LLLL')}`; + } -Template.cardEndDate.events({ - 'click .js-edit-date': Popup.open('editCardEndDate'), -}); + events() { + return super.events().concat({ + 'click .js-edit-date': Popup.open('editCardEndDate'), + }); + } +} +CardEndDate.register('cardEndDate'); -// cardCustomFieldDate -Template.cardCustomFieldDate.onCreated(function () { - cardDateOnCreated(this); - const self = this; - self.autorun(() => { - self.date.set(new Date(Template.currentData().value)); - }); -}); +class CardCustomFieldDate extends CardDate { + template() { + return 'dateCustomField'; + } + + onCreated() { + super.onCreated(); + const self = this; + self.autorun(() => { + self.date.set(new Date(self.data().value)); + }); + } + + showWeek() { + return getISOWeek(this.date.get()).toString(); + } + + showWeekOfYear() { + const user = ReactiveCache.getCurrentUser(); + if (!user) { + // For non-logged-in users, week of year is not shown + return false; + } + return user.isShowWeekOfYear(); + } -Template.cardCustomFieldDate.helpers(cardDateHelpers({ showDate() { - const tpl = Template.instance(); // this will start working once mquandalle:moment // is updated to at least moment.js 2.10.5 // until then, the date is displayed in the "L" format - return tpl.date.get().calendar(null, { + return this.date.get().calendar(null, { sameElse: 'llll', }); - }, + } + showTitle() { - const tpl = Template.instance(); const currentUser = ReactiveCache.getCurrentUser(); const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - const formattedDate = formatDateByUserPreference(tpl.date.get(), dateFormat, true); + const formattedDate = formatDateByUserPreference(this.date.get(), dateFormat, true); return `${formattedDate}`; - }, + } + classes() { return 'customfield-date'; - }, -})); + } -// --- Minicard date templates --- + events() { + return []; + } +} +CardCustomFieldDate.register('cardCustomFieldDate'); -// minicardReceivedDate -Template.minicardReceivedDate.onCreated(function () { - cardDateOnCreated(this); - const self = this; - self.autorun(() => { - self.date.set(new Date(Template.currentData().getReceived())); - }); -}); +(class extends CardReceivedDate { + template() { + return 'minicardReceivedDate'; + } + + showDate() { + const currentUser = ReactiveCache.getCurrentUser(); + const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; + return formatDateByUserPreference(this.date.get(), dateFormat, true); + } +}.register('minicardReceivedDate')); -Template.minicardReceivedDate.helpers(cardDateHelpers({ +(class extends CardStartDate { + template() { + return 'minicardStartDate'; + } + + showDate() { + const currentUser = ReactiveCache.getCurrentUser(); + const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; + return formatDateByUserPreference(this.date.get(), dateFormat, true); + } +}.register('minicardStartDate')); + +(class extends CardDueDate { + template() { + return 'minicardDueDate'; + } + + showDate() { + const currentUser = ReactiveCache.getCurrentUser(); + const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; + return formatDateByUserPreference(this.date.get(), dateFormat, true); + } +}.register('minicardDueDate')); + +(class extends CardEndDate { + template() { + return 'minicardEndDate'; + } + + showDate() { + const currentUser = ReactiveCache.getCurrentUser(); + const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; + return formatDateByUserPreference(this.date.get(), dateFormat, true); + } +}.register('minicardEndDate')); + +(class extends CardCustomFieldDate { + template() { + return 'minicardCustomFieldDate'; + } + + showDate() { + const currentUser = ReactiveCache.getCurrentUser(); + const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; + return formatDateByUserPreference(this.date.get(), dateFormat, true); + } +}.register('minicardCustomFieldDate')); + +class VoteEndDate extends CardDate { + onCreated() { + super.onCreated(); + const self = this; + self.autorun(() => { + self.date.set(new Date(self.data().getVoteEnd())); + }); + } classes() { - const tpl = Template.instance(); - let classes = 'received-date '; - const data = Template.currentData(); - const dueAt = data.getDue(); - const endAt = data.getEnd(); - const startAt = data.getStart(); - const theDate = tpl.date.get(); - - if ( - (startAt && isAfter(theDate, startAt)) || - (endAt && isAfter(theDate, endAt)) || - (dueAt && isAfter(theDate, dueAt)) - ) { - classes += 'overdue'; - } else { - classes += 'not-due'; - } + const classes = 'end-date' + ' '; return classes; - }, - showTitle() { - const tpl = Template.instance(); - const currentUser = ReactiveCache.getCurrentUser(); - const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - const formattedDate = formatDateByUserPreference(tpl.date.get(), dateFormat, true); - return `${TAPi18n.__('card-received-on')} ${formattedDate}`; - }, + } showDate() { const currentUser = ReactiveCache.getCurrentUser(); const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - return formatDateByUserPreference(Template.instance().date.get(), dateFormat, true); - }, -})); + return formatDateByUserPreference(this.date.get(), dateFormat, true); + } + showTitle() { + return `${TAPi18n.__('card-end-on')} ${this.date.get().toLocaleString()}`; + } -Template.minicardReceivedDate.events({ - 'click .js-edit-date': Popup.open('editCardReceivedDate'), -}); + events() { + return super.events().concat({ + 'click .js-edit-date': Popup.open('editVoteEndDate'), + }); + } +} +VoteEndDate.register('voteEndDate'); -// minicardStartDate -Template.minicardStartDate.onCreated(function () { - cardDateOnCreated(this); - const self = this; - self.autorun(() => { - self.date.set(new Date(Template.currentData().getStart())); - }); -}); - -Template.minicardStartDate.helpers(cardDateHelpers({ +class PokerEndDate extends CardDate { + onCreated() { + super.onCreated(); + const self = this; + self.autorun(() => { + self.date.set(new Date(self.data().getPokerEnd())); + }); + } classes() { - const tpl = Template.instance(); - let classes = 'start-date '; - const data = Template.currentData(); - const dueAt = data.getDue(); - const endAt = data.getEnd(); - const theDate = tpl.date.get(); - const nowVal = tpl.now.get(); - - if ((endAt && isAfter(theDate, endAt)) || (dueAt && isAfter(theDate, dueAt))) { - classes += 'overdue'; - } else if (isAfter(theDate, nowVal)) { - classes += 'not-due'; - } else { - classes += 'current'; - } + const classes = 'end-date' + ' '; return classes; - }, - showTitle() { - const tpl = Template.instance(); - const currentUser = ReactiveCache.getCurrentUser(); - const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - const formattedDate = formatDateByUserPreference(tpl.date.get(), dateFormat, true); - return `${TAPi18n.__('card-start-on')} ${formattedDate}`; - }, + } showDate() { const currentUser = ReactiveCache.getCurrentUser(); const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - return formatDateByUserPreference(Template.instance().date.get(), dateFormat, true); - }, -})); - -Template.minicardStartDate.events({ - 'click .js-edit-date': Popup.open('editCardStartDate'), -}); - -// minicardDueDate -Template.minicardDueDate.onCreated(function () { - cardDateOnCreated(this); - const self = this; - self.autorun(() => { - self.date.set(new Date(Template.currentData().getDue())); - }); -}); - -Template.minicardDueDate.helpers(cardDateHelpers({ - classes() { - const tpl = Template.instance(); - let classes = 'due-date '; - const data = Template.currentData(); - const endAt = data.getEnd(); - const theDate = tpl.date.get(); - const nowVal = tpl.now.get(); - - if (endAt && isBefore(endAt, theDate)) { - classes += 'completed-early'; - } else if (endAt) { - classes += 'completed'; - } else { - const daysDiff = diff(theDate, nowVal, 'days'); - - if (daysDiff < 0) { - classes += 'overdue'; - } else if (daysDiff <= 1) { - classes += 'due-soon'; - } else { - classes += 'not-due'; - } - } - - return classes; - }, + return formatDateByUserPreference(this.date.get(), dateFormat, true); + } showTitle() { - const tpl = Template.instance(); - const currentUser = ReactiveCache.getCurrentUser(); - const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - const formattedDate = formatDateByUserPreference(tpl.date.get(), dateFormat, true); - return `${TAPi18n.__('card-due-on')} ${formattedDate}`; - }, - showDate() { - const currentUser = ReactiveCache.getCurrentUser(); - const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - return formatDateByUserPreference(Template.instance().date.get(), dateFormat, true); - }, -})); + return `${TAPi18n.__('card-end-on')} ${format(this.date.get(), 'LLLL')}`; + } -Template.minicardDueDate.events({ - 'click .js-edit-date': Popup.open('editCardDueDate'), -}); - -// minicardEndDate -Template.minicardEndDate.onCreated(function () { - cardDateOnCreated(this); - const self = this; - self.autorun(() => { - self.date.set(new Date(Template.currentData().getEnd())); - }); -}); - -Template.minicardEndDate.helpers(cardDateHelpers({ - classes() { - const tpl = Template.instance(); - let classes = 'end-date '; - const data = Template.currentData(); - const dueAt = data.getDue(); - const theDate = tpl.date.get(); - - if (!dueAt) { - classes += 'completed'; - } else if (isBefore(theDate, dueAt)) { - classes += 'completed-early'; - } else if (isAfter(theDate, dueAt)) { - classes += 'completed-late'; - } else { - classes += 'completed-on-time'; - } - return classes; - }, - showTitle() { - const tpl = Template.instance(); - return `${TAPi18n.__('card-end-on')} ${format(tpl.date.get(), 'LLLL')}`; - }, - showDate() { - const currentUser = ReactiveCache.getCurrentUser(); - const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - return formatDateByUserPreference(Template.instance().date.get(), dateFormat, true); - }, -})); - -Template.minicardEndDate.events({ - 'click .js-edit-date': Popup.open('editCardEndDate'), -}); - -// minicardCustomFieldDate -Template.minicardCustomFieldDate.onCreated(function () { - cardDateOnCreated(this); - const self = this; - self.autorun(() => { - self.date.set(new Date(Template.currentData().value)); - }); -}); - -Template.minicardCustomFieldDate.helpers(cardDateHelpers({ - showDate() { - const currentUser = ReactiveCache.getCurrentUser(); - const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - return formatDateByUserPreference(Template.instance().date.get(), dateFormat, true); - }, - showTitle() { - const tpl = Template.instance(); - const currentUser = ReactiveCache.getCurrentUser(); - const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - const formattedDate = formatDateByUserPreference(tpl.date.get(), dateFormat, true); - return `${formattedDate}`; - }, - classes() { - return 'customfield-date'; - }, -})); - -// --- Vote and Poker end date badge templates --- - -// voteEndDate -Template.voteEndDate.onCreated(function () { - cardDateOnCreated(this); - const self = this; - self.autorun(() => { - self.date.set(new Date(Template.currentData().getVoteEnd())); - }); -}); - -Template.voteEndDate.helpers(cardDateHelpers({ - classes() { - return 'end-date '; - }, - showDate() { - const currentUser = ReactiveCache.getCurrentUser(); - const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - return formatDateByUserPreference(Template.instance().date.get(), dateFormat, true); - }, - showTitle() { - const tpl = Template.instance(); - return `${TAPi18n.__('card-end-on')} ${tpl.date.get().toLocaleString()}`; - }, -})); - -Template.voteEndDate.events({ - 'click .js-edit-date': Popup.open('editVoteEndDate'), -}); - -// pokerEndDate -Template.pokerEndDate.onCreated(function () { - cardDateOnCreated(this); - const self = this; - self.autorun(() => { - self.date.set(new Date(Template.currentData().getPokerEnd())); - }); -}); - -Template.pokerEndDate.helpers(cardDateHelpers({ - classes() { - return 'end-date '; - }, - showDate() { - const currentUser = ReactiveCache.getCurrentUser(); - const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - return formatDateByUserPreference(Template.instance().date.get(), dateFormat, true); - }, - showTitle() { - const tpl = Template.instance(); - return `${TAPi18n.__('card-end-on')} ${format(tpl.date.get(), 'LLLL')}`; - }, -})); - -Template.pokerEndDate.events({ - 'click .js-edit-date': Popup.open('editPokerEndDate'), -}); + events() { + return super.events().concat({ + 'click .js-edit-date': Popup.open('editPokerEndDate'), + }); + } +} +PokerEndDate.register('pokerEndDate'); diff --git a/client/components/cards/cardDescription.js b/client/components/cards/cardDescription.js index 1ce48d4e9..c958c335e 100644 --- a/client/components/cards/cardDescription.js +++ b/client/components/cards/cardDescription.js @@ -1,29 +1,37 @@ const descriptionFormIsOpen = new ReactiveVar(false); -Template.descriptionForm.onDestroyed(function () { - descriptionFormIsOpen.set(false); - $('.note-popover').hide(); -}); +BlazeComponent.extendComponent({ + onDestroyed() { + descriptionFormIsOpen.set(false); + $('.note-popover').hide(); + }, -Template.descriptionForm.helpers({ descriptionFormIsOpen() { return descriptionFormIsOpen.get(); }, -}); -Template.descriptionForm.events({ - async 'submit .js-card-description'(event, tpl) { - event.preventDefault(); - const description = tpl.currentComponent ? tpl.currentComponent().getValue() : tpl.$('textarea').val(); - await this.setDescription(description); + getInput() { + return this.$('.js-new-description-input'); }, - // Pressing Ctrl+Enter should submit the form - 'keydown form textarea'(evt, tpl) { - if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) { - const submitButton = tpl.find('button[type=submit]'); - if (submitButton) { - submitButton.click(); - } - } + + events() { + return [ + { + 'submit .js-card-description'(event) { + event.preventDefault(); + const description = this.currentComponent().getValue(); + this.data().setDescription(description); + }, + // Pressing Ctrl+Enter should submit the form + 'keydown form textarea'(evt) { + if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) { + const submitButton = this.find('button[type=submit]'); + if (submitButton) { + submitButton.click(); + } + } + }, + }, + ]; }, -}); +}).register('descriptionForm'); diff --git a/client/components/cards/cardDetails.css b/client/components/cards/cardDetails.css index 2cce7a9c8..fb68b2957 100644 --- a/client/components/cards/cardDetails.css +++ b/client/components/cards/cardDetails.css @@ -1,25 +1,23 @@ /* Date Format Selector */ .card-details-item-date-format { - margin-bottom: 12px; + margin-bottom: 10px; } .card-details-item-date-format .card-details-item-title { - font-size: 15px; + font-size: 14px; font-weight: bold; - margin-bottom: 6px; + margin-bottom: 5px; color: #333; - letter-spacing: 0.03em; } .card-details-item-date-format .js-date-format-selector { width: 100%; - padding: 9px 10px; + padding: 8px; border: 1px solid #ddd; - border-radius: 5px; + border-radius: 4px; background-color: #fff; - font-size: 15px; + font-size: 14px; cursor: pointer; - transition: border-color 0.15s, box-shadow 0.15s; } .card-details-item-date-format .js-date-format-selector:focus { @@ -29,18 +27,18 @@ } .assignee { + border-radius: 3px; display: block; position: relative; float: left; - height: clamp(24px, 3.5vw, 36px); - width: clamp(24px, 3.5vw, 36px); - margin: 0.3vh; + height: 30px; + width: 30px; + margin: .3vh; cursor: pointer; user-select: none; z-index: 1; text-decoration: none; border-radius: 50%; - box-shadow: 0 1px 2px 0 rgba(0,0,0,0.04); } .assignee .avatar { overflow: hidden; @@ -53,18 +51,12 @@ background-color: #dbdbdb; color: #444; position: absolute; - text-align: center; - display: flex; - align-items: center; - justify-content: center; - font-weight: bold; } .assignee .avatar.avatar-image { object-fit: cover; object-position: center; height: 100%; width: 100%; - display: block; } .assignee .assignee-presence-status { background-color: #b3b3b3; @@ -75,6 +67,7 @@ position: absolute; right: -1px; bottom: -1px; + border: 1px solid #fff; z-index: 15; } .assignee .assignee-presence-status.active { @@ -98,7 +91,6 @@ align-items: center; justify-content: center; box-shadow: 0 0 0 2px #bfbfbf inset; - transition: box-shadow 0.12s; } .assignee.add-assignee:hover, .assignee.add-assignee.is-active { @@ -110,83 +102,22 @@ background-color: rgba(0,0,0,0.875); color: #fff; border-radius: 0.7vw; - font-size: 0.98em; } - .card-details { padding: 0; flex-shrink: 0; flex-basis: min(600px, 80vw); will-change: flex-basis; - overflow-y: auto; + overflow-y: scroll; overflow-x: hidden; background: #f7f7f7; - border-radius: 0 0 0.4vw 0.4vw; + border-radius: bottom 0.4vw; z-index: 30; animation: flexGrowIn 0.1s; box-shadow: 0 0 0.9vh 0 #b3b3b3; - transition: flex-basis 0.1s, box-shadow 0.15s; + transition: flex-basis 0.1s; box-sizing: border-box; } - -/* Desktop mode: position card below board header */ -body.desktop-mode .card-details:not(.card-details-popup) { - position: fixed; - width: auto; - max-width: 800px; - flex-basis: auto; - border-radius: 8px; - z-index: 100; -} - -/* Default position for first card or when dragged */ -body.desktop-mode .card-details:not(.card-details-popup):not([style*="left"]):not([style*="top"]) { - top: 50px; - left: 20px; - right: 20px; - bottom: 20px; -} - -/* Stagger positions for multiple cards using nth-of-type */ -body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(1) { - top: 50px; - left: 20px; -} -body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(2) { - top: 80px; - left: 50px; -} -body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(3) { - top: 110px; - left: 80px; -} -body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(4) { - top: 140px; - left: 110px; -} -body.desktop-mode .card-details:not(.card-details-popup):nth-of-type(5) { - top: 170px; - left: 140px; -} - -/* For expanded cards, set dimensions */ -body.desktop-mode .card-details:not(.card-details-popup):not(.card-details-collapsed) { - right: 20px; - bottom: 20px; -} - -/* Collapsed card state - hide content and set height to title row only */ -.card-details.card-details-collapsed .card-details-canvas > *:not(.card-details-header) { - display: none !important; -} -.card-details.card-details-collapsed { - height: auto !important; - bottom: auto !important; - overflow: visible; -} -body.desktop-mode .card-details.card-details-collapsed { - bottom: auto !important; -} .card-details .mCustomScrollBox { padding-left: 0; } @@ -196,47 +127,18 @@ body.desktop-mode .card-details.card-details-collapsed { } .card-details .card-details-header { margin: 0 -20px 5px; - padding: 8px 20px; + padding: 7px 20px; background: #ededed; border-bottom: 1px solid #dbdbdb; position: sticky; top: 0px; z-index: 500; - display: flow-root; - min-height: 44px; } .card-details .card-details-header .card-number { color: #b3b3b3; display: inline-block; - margin-right: 6px; -} - -/* Collapse toggle triangle */ -.card-details .card-details-header .card-collapse-toggle { - float: left; - font-size: 20px; - padding: 7px 10px; - margin-left: -10px; margin-right: 5px; - cursor: pointer; - user-select: none; - color: #000; - vertical-align: middle; - line-height: 1.2; } - -.card-details .card-details-header .card-drag-handle { - font-size: 20px; - padding: 8px 10px; - margin-right: 10px; - cursor: move; - user-select: none; - display: inline-block; - float: right; - vertical-align: middle; - line-height: 1.2; -} - .card-details .card-details-header .close-card-details, .card-details .card-details-header .maximize-card-details, .card-details .card-details-header .minimize-card-details, @@ -254,19 +156,11 @@ body.desktop-mode .card-details.card-details-collapsed { font-size: 24px; padding: 5px 10px 5px 10px; margin-right: -8px; - cursor: pointer; - user-select: none; - vertical-align: middle; - line-height: 1.2; - transition: color 0.13s; } -.card-details .card-details-header .close-card-details-mobile-web, -.card-details .card-details-header .card-mobile-desktop-toggle { +.card-details .card-details-header .close-card-details-mobile-web { font-size: 24px; padding: 5px; - margin-right: 5px; - cursor: pointer; - user-select: none; + margin-right: 40px; } .card-details .card-details-header .card-copy-button { font-size: 17px; @@ -281,44 +175,12 @@ body.desktop-mode .card-details.card-details-collapsed { .card-details .card-details-header .card-details-menu { font-size: 17px; padding: 10px; - vertical-align: middle; - line-height: 1.2; } .card-details .card-details-header .card-details-menu-mobile-web { font-size: 17px; padding: 10px; margin-right: 30px; } -.card-details .card-details-header .card-mobile-desktop-toggle, -.card-details .card-details-header .card-zoom-in, -.card-details .card-details-header .card-zoom-out { - font-size: 24px; - padding: 5px 10px 5px 10px; - margin-right: 5px; - cursor: pointer; - user-select: none; - float: right; -} - -/* Unify all card text to match title size */ -.card-details { - font-size: 1em; -} -.card-details p, -.card-details span, -.card-details div, -.card-details a, -.card-details label, -.card-details input, -.card-details textarea, -.card-details select, -.card-details button, -.card-details .card-details-item-title, -.card-details .card-label, -.card-details .viewer { - font-size: inherit; - line-height: 1.5; -} .card-details .card-details-header .card-details-watch { font-size: 17px; padding-left: 7px; @@ -326,13 +188,9 @@ body.desktop-mode .card-details.card-details-collapsed { } .card-details .card-details-header .card-details-title { font-weight: bold; - font-size: 1.35em; + font-size: 1.33em; margin: 7px 0 0; padding: 0; - display: inline-block; - vertical-align: middle; - line-height: 1.3; - letter-spacing: 0.01em; } .card-details .card-details-header .linked-card-location { font-style: italic; @@ -347,10 +205,10 @@ body.desktop-mode .card-details.card-details-collapsed { margin-bottom: 10px; } .card-details .card-details-header form.inlined-form .copied-tooltip { - padding: 0 10px; + padding: 0px 10px; } .card-details .card-details-header .card-details-list { - font-size: 0.9em; + font-size: 0.85em; margin-bottom: 3px; } .card-details .card-details-header .card-details-list a.card-details-list-title { @@ -360,7 +218,7 @@ body.desktop-mode .card-details.card-details-collapsed { display: inline-block; background: #e6e6e6; border-radius: 3px; - padding: 0 5px; + padding: 0px 5px; } .card-details .card-details-header .copied-tooltip { margin-right: 10px; @@ -371,13 +229,11 @@ body.desktop-mode .card-details.card-details-collapsed { } .card-details .card-description textarea { min-height: 100px; - resize: vertical; } .card-details .card-details-items { display: flex; flex-wrap: wrap; margin: 15px 0; - gap: 0.5em; } .card-details .card-details-items .card-details-item { margin-right: 0.5em; @@ -428,28 +284,15 @@ body.desktop-mode .card-details.card-details-collapsed { position: fixed; resize: both; } - - /* Override for mobile mode even on larger screens */ - body.mobile-mode .card-details { - width: 100vw !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - bottom: 0 !important; - height: 100vh !important; - max-height: 100vh !important; - resize: none !important; - } - .card-details-maximized { padding: 0; flex-shrink: 0; flex-basis: calc(100% - 20px); will-change: flex-basis; - overflow-y: auto; - overflow-x: auto; + overflow-y: scroll; + overflow-x: scroll; background: #f7f7f7; - border-radius: 0 0 3px 3px; + border-radius: bottom 3px; z-index: 100; animation: flexGrowIn 0.1s; box-shadow: 0 0 7px 0 #b3b3b3; @@ -492,52 +335,19 @@ input[type="submit"].attachment-add-link-submit { } @media screen and (max-width: 800px) { .card-details { - width: 100% !important; - padding: 0 !important; - margin: 0 !important; + width: calc(100% - 1px); + padding: 0px 20px 0px 20px; + margin: 0px; transition: none; - overflow-y: auto; - overflow-x: hidden; - -webkit-overflow-scrolling: touch; - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - bottom: 0 !important; - z-index: 100 !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - box-shadow: none !important; - } - - /* Ensure card details are above everything on mobile */ - body.mobile-mode .card-details { - z-index: 100 !important; - width: 100vw !important; - left: 0 !important; - right: 0 !important; + overflow-y: revert; + overflow-x: revert; } .card-details .card-details-canvas { width: 100%; padding-left: 0px; - padding: 0 15px; } .card-details .card-details-header .close-card-details { margin-right: 0px; - display: block !important; - } - .card-details .card-details-header .close-card-details-mobile-web { - display: block !important; - margin-right: 5px !important; - } - .card-details .card-details-header .card-mobile-desktop-toggle { - display: block !important; - margin-right: 5px !important; - } - .card-details .card-details-header .card-mobile-desktop-toggle { - display: block !important; - margin-right: 5px !important; } .card-details .card-details-header .card-details-menu { margin-right: 40px; @@ -563,62 +373,6 @@ input[type="submit"].attachment-add-link-submit { .pop-over > .content-wrapper > .popup-container-depth-0 .card-details-header { margin: 0; } - /* iPhone mobile: enlarge header buttons and increase spacing */ - body.mobile-mode.iphone-device .card-details .card-details-header { - padding-right: 16px; - } - body.mobile-mode.iphone-device .card-details .card-details-header .close-card-details, - body.mobile-mode.iphone-device .card-details .card-details-header .maximize-card-details, - body.mobile-mode.iphone-device .card-details .card-details-header .minimize-card-details, - body.mobile-mode.iphone-device .card-details .card-details-header .card-details-menu-mobile-web, - body.mobile-mode.iphone-device .card-details .card-details-header .card-copy-mobile-button, - body.mobile-mode.iphone-device .card-details .card-details-header .card-mobile-desktop-toggle, - body.mobile-mode.iphone-device .card-details .card-details-header .card-zoom-in, - body.mobile-mode.iphone-device .card-details .card-details-header .card-zoom-out { - font-size: 2em !important; /* 2x bigger */ - padding: 0.3em !important; - margin-right: 0.75em !important; /* 2x space compared to default */ - margin-left: 0 !important; - } - /* Avoid clipping of the close button on the right edge */ - body.mobile-mode.iphone-device .card-details .card-details-header .close-card-details { - margin-right: 0.75em !important; - } - /* Enlarge the header title too */ - body.mobile-mode.iphone-device .card-details .card-details-header .card-details-title { - font-size: 1.2em !important; - font-weight: bold; - } -} - -/* Mobile mode styles - apply when body has mobile-mode class regardless of screen size */ -body.mobile-mode .card-details { - width: 100vw !important; - padding: 0px !important; - margin: 0px !important; - position: fixed !important; - top: 0 !important; - left: 0 !important; - right: 0 !important; - bottom: 0 !important; - z-index: 100 !important; - height: 100vh !important; - max-height: 100vh !important; - border-radius: 0 !important; - box-shadow: none !important; - overflow-y: auto !important; - overflow-x: hidden !important; - -webkit-overflow-scrolling: touch; -} - -body.mobile-mode .card-details .card-details-canvas { - width: 100% !important; - padding: 0 15px !important; -} - -body.mobile-mode .card-details .card-details-header .close-card-details, -body.mobile-mode .card-details .card-details-header .close-card-details-mobile-web { - display: block !important; } .card-details-white { background: #fff !important; @@ -727,15 +481,13 @@ body.mobile-mode .card-details .card-details-header .close-card-details-mobile-w .vote-title { display: flex; justify-content: space-between; - align-items: center; } .vote-title .js-edit-date { - align-self: flex-start; - margin-left: 6px; + align-self: baseline; + margin-left: 5px; } .vote-result { display: flex; - gap: 6px; } .js-show-positive-votes { cursor: pointer; @@ -746,33 +498,29 @@ body.mobile-mode .card-details .card-details-header .close-card-details-mobile-w .poker-title { display: flex; justify-content: space-between; - align-items: center; } .poker-title .js-edit-date { - align-self: flex-start; - margin-left: 6px; + align-self: baseline; + margin-left: 5px; } .poker-result { display: flex; - flex-wrap: wrap; - gap: 7px; + flex-flow: row wrap; } .js-show-positive-poker-votes { cursor: pointer; } .poker-deck { display: grid; - grid-auto-flow: row; + flex-direction: column; text-align: center; - gap: 6px; } .poker-card-result { - width: 34px; + width: 32px; font-size: 1em; font-weight: bold; - padding: 4px 2px; + padding: 4px 2px 4px 2px; cursor: default; - border-radius: 3px; } .winner { font-weight: bold; @@ -783,7 +531,6 @@ body.mobile-mode .card-details .card-details-header .close-card-details-mobile-w } .responsive-table { overflow-x: auto; - width: 100%; } .poker-table { display: table; @@ -846,15 +593,11 @@ body.mobile-mode .card-details .card-details-header .close-card-details-mobile-w margin: auto; margin-right: 10px; width: 100px; - border-radius: 2px; - padding: 3px 6px; } .estimation-add button { display: inline-block; float: right; margin: auto; - border-radius: 2px; - padding: 3px 10px; } .poker-card { width: 48px; @@ -873,7 +616,6 @@ body.mobile-mode .card-details .card-details-header .close-card-details-mobile-w text-align: center; position: relative; cursor: pointer; - transition: box-shadow 0.12s; } .poker-card .inner { display: table-cell; diff --git a/client/components/cards/cardDetails.jade b/client/components/cards/cardDetails.jade index 1a7e9cd0d..90b3fef31 100644 --- a/client/components/cards/cardDetails.jade +++ b/client/components/cards/cardDetails.jade @@ -5,65 +5,48 @@ template(name="cardDetails") +attachmentViewer - section.card-details.js-card-details.nodragscroll(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}' class='{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}' class='{{#if cardCollapsed}}card-details-collapsed{{/if}}'): .card-details-canvas + section.card-details.js-card-details.nodragscroll(class='{{#if cardMaximized}}card-details-maximized{{/if}}' class='{{#if isPopup}}card-details-popup{{/if}}' class='{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}'): .card-details-canvas .card-details-header(class='{{#if colorClass}}card-details-{{colorClass}}{{/if}}') +inlinedForm(classNames="js-card-details-title") +editCardTitleForm else unless isMiniScreen unless isPopup - span.card-collapse-toggle.js-card-collapse-toggle(title="{{_ 'collapse-card'}}") - if cardCollapsed - i.fa.fa-caret-right - else - i.fa.fa-caret-down a.close-card-details.js-close-card-details(title="{{_ 'close-card'}}") - i.fa.fa-times-thin - if cardMaximized - a.fa.fa-window-minimize.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}") - else - a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}") + | ❌ + if canModifyCard + if cardMaximized + a.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}") + | 🔽 + else + a.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}") + | 🔼 + if canModifyCard a.card-details-menu.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") - i.fa.fa-bars + | ☰ a.card-copy-button.js-copy-link( id="cardURL_copy" + class="fa-link" title="{{_ 'copy-card-link-to-clipboard'}}" href="{{ originRelativeUrl }}" ) - span.emoji-icon - i.fa.fa-link - if canModifyCard - span.card-drag-handle.js-card-drag-handle(title="Drag card") - i.fa.fa-arrows span.copied-tooltip {{_ 'copied'}} else - a.close-card-details.js-close-card-details(title="{{_ 'close-card'}}") - i.fa.fa-times-thin - a.card-zoom-out.js-card-zoom-out(title="{{_ 'zoom-out'}}") - i.fa.fa-search-minus - a.card-zoom-in.js-card-zoom-in(title="{{_ 'zoom-in'}}") - i.fa.fa-search-plus - a.card-mobile-desktop-toggle.js-card-mobile-desktop-toggle(title="{{_ 'mobile-desktop-toggle'}}") - if mobileMode - i.fa.fa-desktop - else - i.fa.fa-mobile - if cardMaximized - a.fa.fa-window-minimize.minimize-card-details.js-minimize-card-details(title="{{_ 'minimize-card'}}") - else - a.fa.fa-window-maximize.maximize-card-details.js-maximize-card-details(title="{{_ 'maximize-card'}}") - a.card-details-menu-mobile-web.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") - i.fa.fa-bars - a.card-copy-mobile-button.js-copy-link( - id="cardURL_copy" - title="{{_ 'copy-card-link-to-clipboard'}}" - href="{{ originRelativeUrl }}" - ) - span.emoji-icon - i.fa.fa-link - span.copied-tooltip {{_ 'copied'}} + unless isPopup + a.close-card-details.js-close-card-details(title="{{_ 'close-card'}}") + | ❌ + if canModifyCard + a.card-details-menu-mobile-web.js-open-card-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") + | ☰ + a.card-copy-mobile-button.js-copy-link( + id="cardURL_copy" + class="fa-link" + title="{{_ 'copy-card-link-to-clipboard'}}" + href="{{ originRelativeUrl }}" + ) + span.copied-tooltip {{_ 'copied'}} h2.card-details-title.js-card-title( - class="{{#if canModifyCard}}js-open-inlined-form is-editable{{else}}js-card-title-drag-handle{{/if}}") + class="{{#if canModifyCard}}js-open-inlined-form is-editable{{/if}}") +viewer if currentBoard.allowsCardNumber span.card-number @@ -71,7 +54,7 @@ template(name="cardDetails") = getTitle if isWatching i.card-details-watch - i.fa.fa-eye + | 👁️ .card-details-path each parentList |   >   @@ -93,7 +76,7 @@ template(name="cardDetails") if hasActiveUploads .card-details-upload-progress .upload-progress-header - i.fa.fa-upload + | 📤 span {{_ 'uploading-files'}} ({{uploadCount}}) each uploads .upload-progress-item(class="{{#if $eq status 'error'}}upload-error{{/if}}") @@ -102,11 +85,11 @@ template(name="cardDetails") .upload-progress-fill(style="width: {{progress}}%") if $eq status 'error' .upload-progress-error - i.fa.fa-exclamation-triangle + | ⚠️ span {{_ 'upload-failed'}} else if $eq status 'completed' .upload-progress-success - i.fa.fa-check + | ✅ span {{_ 'upload-completed'}} .card-details-left @@ -115,7 +98,7 @@ template(name="cardDetails") if currentBoard.allowsLabels .card-details-item.card-details-item-labels h3.card-details-item-title - i.fa.fa-tags + | 🏷️ | {{_ 'labels'}} a(class="{{#if canModifyCard}}js-add-labels{{else}}is-disabled{{/if}}" title="{{_ 'card-labels-title'}}") each labels @@ -125,14 +108,14 @@ template(name="cardDetails") if canModifyCard unless currentUser.isWorker a.card-label.add-label.js-add-labels(title="{{_ 'card-labels-title'}}") - i.fa.fa-plus + | ➕ if currentBoard.hasAnyAllowsDate hr .card-details-item.card-details-item-date-format h3.card-details-item-title - i.fa.fa-calendar + | 📅 | {{_ 'date-format'}} .card-details-item-content select.js-date-format-selector @@ -143,7 +126,7 @@ template(name="cardDetails") if currentBoard.allowsReceivedDate .card-details-item.card-details-item-received h3.card-details-item-title - i.fa.fa-sign-out + | 📥 | {{_ 'card-received'}} if getReceived +cardReceivedDate @@ -151,12 +134,12 @@ template(name="cardDetails") if canModifyCard unless currentUser.isWorker a.card-label.add-label.js-received-date - i.fa.fa-plus + | ➕ if currentBoard.allowsStartDate .card-details-item.card-details-item-start h3.card-details-item-title - i.fa.fa-hourglass-start + | 🚀 | {{_ 'card-start'}} if getStart +cardStartDate @@ -164,12 +147,12 @@ template(name="cardDetails") if canModifyCard unless currentUser.isWorker a.card-label.add-label.js-start-date - i.fa.fa-plus + | ➕ if currentBoard.allowsDueDate .card-details-item.card-details-item-due h3.card-details-item-title - i.fa.fa-clock-o + | ⏰ | {{_ 'card-due'}} if getDue +cardDueDate @@ -177,12 +160,12 @@ template(name="cardDetails") if canModifyCard unless currentUser.isWorker a.card-label.add-label.js-due-date - i.fa.fa-plus + | ➕ if currentBoard.allowsEndDate .card-details-item.card-details-item-end h3.card-details-item-title - i.fa.fa-hourglass-end + | 🏁 | {{_ 'card-end'}} if getEnd +cardEndDate @@ -190,7 +173,7 @@ template(name="cardDetails") if canModifyCard unless currentUser.isWorker a.card-label.add-label.js-end-date - i.fa.fa-plus + | ➕ if currentBoard.hasAnyAllowsUser hr @@ -198,7 +181,7 @@ template(name="cardDetails") if currentBoard.allowsCreator .card-details-item.card-details-item-creator h3.card-details-item-title - i.fa.fa-user + | 👤 | {{_ 'creator'}} +userAvatar(userId=userId noRemove=true) @@ -208,7 +191,7 @@ template(name="cardDetails") if currentBoard.allowsMembers .card-details-item.card-details-item-members h3.card-details-item-title - i.fa.fa-users + | 👤s | {{_ 'members'}} each userId in getMembers +userAvatar(userId=userId cardId=_id) @@ -216,30 +199,30 @@ template(name="cardDetails") if canModifyCard unless currentUser.isWorker a.member.add-member.card-details-item-add-button.js-add-members(title="{{_ 'card-members-title'}}") - i.fa.fa-plus + | ➕ //if assigneeSelected if currentBoard.allowsAssignee .card-details-item.card-details-item-assignees h3.card-details-item-title - i.fa.fa-user + | 👤 | {{_ 'assignee'}} each userId in getAssignees +userAvatar(userId=userId cardId=_id assignee=true) | {{! XXX Hack to hide syntaxic coloration /// }} if canModifyCard a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}") - i.fa.fa-plus + | ➕ if currentUser.isWorker unless assigneeSelected a.assignee.add-assignee.card-details-item-add-button.js-add-assignees(title="{{_ 'assignee'}}") - i.fa.fa-plus + | ➕ //.card-details-items if currentBoard.allowsRequestedBy .card-details-item.card-details-item-name h3.card-details-item-title - i.fa.fa-shopping-cart + | 🛒 | {{_ 'requested-by'}} if canModifyCard unless currentUser.isWorker @@ -259,7 +242,7 @@ template(name="cardDetails") if currentBoard.allowsAssignedBy .card-details-item.card-details-item-name h3.card-details-item-title - i.fa.fa-user-plus + | 👤-plus | {{_ 'assigned-by'}} if canModifyCard unless currentUser.isWorker @@ -282,7 +265,7 @@ template(name="cardDetails") if currentBoard.allowsCardSortingByNumber .card-details-item.card-details-sort-order h3.card-details-item-title - i.fa.fa-sort-numeric-asc + | 🔢 | {{_ 'sort'}} if canModifyCard +inlinedForm(classNames="js-card-details-sort") @@ -295,7 +278,7 @@ template(name="cardDetails") if currentBoard.allowsShowLists .card-details-item.card-details-show-lists h3.card-details-item-title - i.fa.fa-list + | 📋 | {{_ 'list'}} select.js-select-card-details-lists(disabled="{{#unless canModifyCard}}disabled{{/unless}}") each currentBoard.lists @@ -321,7 +304,7 @@ template(name="cardDetails") hr .card-details-item.card-details-item-customfield h3.card-details-item-title - i.fa.fa-list + | 📋-alt = definition.name +cardCustomField @@ -339,7 +322,7 @@ template(name="cardDetails") .vote-title div.flex h3 - i.fa.fa-thumbs-up + | 👍 | {{_ 'vote-question'}} if getVoteEnd +voteEndDate @@ -351,14 +334,13 @@ template(name="cardDetails") .card-label.card-label-green {{ voteCountPositive }} .card-label.card-label-red {{ voteCountNegative }} unless ($and currentBoard.isPublic voteAllowNonBoardMembers ) - .card-label.card-label-gray - | {{ voteCount }} {{_ 'r-of' }} {{ currentBoard.activeMembers.length }} + .card-label.card-label-gray {{ voteCount }} {{_ 'r-of' }} {{ currentBoard.activeMembers.length }} +viewer = getVoteQuestion if showVotingButtons button.card-details-green.js-vote.js-vote-positive(class="{{#if voteState}}voted{{/if}}") if voteState - i.fa.fa-thumbs-up + | 👍 | {{_ 'vote-for-it'}} button.card-details-red.js-vote.js-vote-negative(class="{{#if $eq voteState false}}voted{{/if}}") if $eq voteState false @@ -370,7 +352,7 @@ template(name="cardDetails") .poker-title div.flex h3 - i.fa.fa-thumbs-up + | 👍 | {{_ 'poker-question'}} if getPokerEnd +pokerEndDate @@ -378,60 +360,59 @@ template(name="cardDetails") .poker-result if expiredPoker unless ($and currentBoard.isPublic pokerAllowNonBoardMembers ) - .card-label.card-label-gray - | {{ pokerCount }} {{_ 'r-of' }} {{ currentBoard.activeMembers.length }} + .card-label.card-label-gray {{ pokerCount }} {{_ 'r-of' }} {{ currentBoard.activeMembers.length }} if showPlanningPokerButtons .poker-result .poker-deck .poker-card span.inner.js-poker.js-poker-vote-one(class="{{#if $eq pokerState 'one'}}poker-voted{{/if}}") {{_ 'poker-one'}} if $eq pokerState "one" - i.fa.fa-check + | ✅ .poker-deck .poker-card span.inner.js-poker.js-poker-vote-two(class="{{#if $eq pokerState 'two'}}poker-voted{{/if}}") {{_ 'poker-two'}} if $eq pokerState "two" - i.fa.fa-check + | ✅ .poker-deck .poker-card span.inner.js-poker.js-poker-vote-three(class="{{#if $eq pokerState 'three'}}poker-voted{{/if}}") {{_ 'poker-three'}} if $eq pokerState "three" - i.fa.fa-check + | ✅ .poker-deck .poker-card span.inner.js-poker.js-poker-vote-five(class="{{#if $eq pokerState 'five'}}poker-voted{{/if}}") {{_ 'poker-five'}} if $eq pokerState "five" - i.fa.fa-check + | ✅ .poker-deck .poker-card span.inner.js-poker.js-poker-vote-eight(class="{{#if $eq pokerState 'eight'}}poker-voted{{/if}}") {{_ 'poker-eight'}} if $eq pokerState "eight" - i.fa.fa-check + | ✅ .poker-deck .poker-card span.inner.js-poker.js-poker-vote-thirteen(class="{{#if $eq pokerState 'thirteen'}}poker-voted{{/if}}") {{_ 'poker-thirteen'}} if $eq pokerState "thirteen" - i.fa.fa-check + | ✅ .poker-deck .poker-card span.inner.js-poker.js-poker-vote-twenty(class="{{#if $eq pokerState 'twenty'}}poker-voted{{/if}}") {{_ 'poker-twenty'}} if $eq pokerState "twenty" - i.fa.fa-check + | ✅ .poker-deck .poker-card span.inner.js-poker.js-poker-vote-forty(class="{{#if $eq pokerState 'forty'}}poker-voted{{/if}}") {{_ 'poker-forty'}} if $eq pokerState "forty" - i.fa.fa-check + | ✅ .poker-deck .poker-card span.inner.js-poker.js-poker-vote-one-hundred(class="{{#if $eq pokerState 'oneHundred'}}poker-voted{{/if}}") {{_ 'poker-oneHundred'}} if $eq pokerState "oneHundred" - i.fa.fa-check + | ✅ .poker-deck .poker-card span.inner.js-poker.js-poker-vote-unsure(class="{{#if $eq pokerState 'unsure'}}poker-voted{{/if}}") {{_ 'poker-unsure'}} if $eq pokerState "unsure" - i.fa.fa-check + | ✅ if currentUser.isBoardAdmin button.card-details-blue.js-poker-finish(class="{{#if $eq voteState false}}poker-voted{{/if}}") {{_ 'poker-finish'}} @@ -561,7 +542,7 @@ template(name="cardDetails") button.card-details-red.js-poker-replay(class="{{#if $eq voteState false}}voted{{/if}}") {{_ 'poker-replay'}} div.estimation-add button.js-poker-estimation - i.fa.fa-plus + | ➕ | {{_ 'set-estimation'}} input(type=text,autofocus value=getPokerEstimation,id="pokerEstimation") @@ -571,7 +552,7 @@ template(name="cardDetails") if currentBoard.allowsDescriptionTitle hr h3.card-details-item-title - i.fa.fa-file-text-o + | 📝 | {{_ 'description'}} if currentBoard.allowsDescriptionText +inlinedCardDescription(classNames="card-description js-card-description") @@ -582,7 +563,7 @@ template(name="cardDetails") else if currentBoard.allowsDescriptionText a.js-open-inlined-form(title="{{_ 'edit'}}" value=title) - i.fa.fa-pencil-square-o + | ✏️ a.js-open-inlined-form(title="{{_ 'edit'}}" value=title) if getDescription +viewer @@ -612,7 +593,7 @@ template(name="cardDetails") if currentBoard.allowsAttachments hr h3.card-details-item-title - i.fa.fa-paperclip + | 📎 | {{_ 'attachments'}} if Meteor.settings.public.attachmentsUploadMaxSize | {{_ 'max-upload-filesize'}} {{Meteor.settings.public.attachmentsUploadMaxSize}} @@ -628,24 +609,22 @@ template(name="cardDetails") unless currentUser.isNoComments .comment-title h3.card-details-item-title - i.fa.fa-comment-o + | 💬 | {{_ 'comments'}} if currentBoard.allowsComments if currentUser.isBoardMember unless currentUser.isNoComments - unless currentUser.isReadOnly - unless currentUser.isReadAssignedOnly - +commentForm + +commentForm +comments hr .card-details-right - if currentUser.isBoardAdmin + unless currentUser.isNoComments .activity-title h3.card-details-item-title - i.fa.fa-history + | 📜 | {{ _ 'activities'}} if currentUser.isBoardMember .material-toggle-switch(title="{{_ 'show-activities'}}") @@ -655,7 +634,7 @@ template(name="cardDetails") input.toggle-switch(type="checkbox" id="toggleShowActivitiesCard") label.toggle-label(for="toggleShowActivitiesCard") - if currentUser.isBoardAdmin + unless currentUser.isNoComments if isLoaded.get if isLinkedCard +activities(card=this mode="linkedcard") @@ -696,10 +675,10 @@ template(name="cardDetailsActionsPopup") li a.js-toggle-watch-card if isWatching - i.fa.fa-eye + | 👁️ | {{_ 'unwatch'}} else - i.fa.fa-eye + | 👁️-slash | {{_ 'watch'}} hr if canModifyCard @@ -710,16 +689,16 @@ template(name="cardDetailsActionsPopup") //li: a.js-attachments {{_ 'card-edit-attachments'}} li a.js-start-voting - i.fa.fa-thumbs-up + | 👍 | {{_ 'card-edit-voting'}} li a.js-start-planning-poker - i.fa.fa-thumbs-up + | 👍 | {{_ 'card-edit-planning-poker'}} if currentUser.isBoardAdmin li a.js-custom-fields - i.fa.fa-list + | 📋-alt | {{_ 'card-edit-custom-fields'}} //li: a.js-received-date {{_ 'editCardReceivedDatePopup-title'}} //li: a.js-start-date {{_ 'editCardStartDatePopup-title'}} @@ -727,260 +706,114 @@ template(name="cardDetailsActionsPopup") //li: a.js-end-date {{_ 'editCardEndDatePopup-title'}} li a.js-spent-time - i.fa.fa-clock-o + | 🕐 | {{_ 'editCardSpentTimePopup-title'}} li a.js-set-card-color - i.fa.fa-paint-brush + | 🎨 | {{_ 'setCardColorPopup-title'}} li a.js-toggle-show-list-on-minicard if showListOnMinicard - i.fa.fa-eye + | 👁️ | {{_ 'hide-list-on-minicard'}} else - i.fa.fa-eye + | 👁️-slash | {{_ 'show-list-on-minicard'}} - if canModifyCard - hr - else - unless currentUser.isReadOnly - unless currentUser.isReadAssignedOnly - hr + hr ul.pop-over-list li a.js-export-card - i.fa.fa-upload + | 📤 | {{_ 'export-card'}} - unless canModifyCard - unless currentUser.isReadOnly - unless currentUser.isReadAssignedOnly - hr - ul.pop-over-list - li - a.js-move-card-to-top - i.fa.fa-arrow-up - | {{_ 'moveCardToTop-title'}} - li - a.js-move-card-to-bottom - i.fa.fa-arrow-down - | {{_ 'moveCardToBottom-title'}} - hr - ul.pop-over-list - if currentUser.isBoardAdmin - li - a.js-move-card - i.fa.fa-arrow-right - | {{_ 'moveCardPopup-title'}} - unless currentUser.isWorker - li - a.js-copy-card - i.fa.fa-clipboard - | {{_ 'copyCardPopup-title'}} - unless currentUser.isWorker - ul.pop-over-list - li - a.js-copy-checklist-cards - i.fa.fa-copy - | {{_ 'copyManyCardsPopup-title'}} - unless archived - hr - ul.pop-over-list - li - a.js-archive - i.fa.fa-archive - | {{_ 'archive-card'}} - hr - ul.pop-over-list - li - a.js-more - span.emoji-icon - i.fa.fa-link - | {{_ 'cardMorePopup-title'}} - if canModifyCard - hr - ul.pop-over-list + hr + ul.pop-over-list + li + a.js-move-card-to-top + | ⬆️ + | {{_ 'moveCardToTop-title'}} + li + a.js-move-card-to-bottom + | ⬇️ + | {{_ 'moveCardToBottom-title'}} + hr + ul.pop-over-list + if currentUser.isBoardAdmin li - a.js-move-card-to-top - i.fa.fa-arrow-up - | {{_ 'moveCardToTop-title'}} - li - a.js-move-card-to-bottom - i.fa.fa-arrow-down - | {{_ 'moveCardToBottom-title'}} - hr - ul.pop-over-list - if currentUser.isBoardAdmin - li - a.js-move-card - i.fa.fa-arrow-right - | {{_ 'moveCardPopup-title'}} - unless currentUser.isWorker - li - a.js-copy-card - i.fa.fa-clipboard - | {{_ 'copyCardPopup-title'}} + a.js-move-card + | ➡️ + | {{_ 'moveCardPopup-title'}} unless currentUser.isWorker - ul.pop-over-list - li - a.js-copy-checklist-cards - i.fa.fa-copy - | {{_ 'copyManyCardsPopup-title'}} - unless archived - hr - ul.pop-over-list - li - a.js-archive - i.fa.fa-archive - | {{_ 'archive-card'}} + li + a.js-copy-card + | 📋 + | {{_ 'copyCardPopup-title'}} + unless currentUser.isWorker + ul.pop-over-list + li + a.js-copy-checklist-cards + | 📋 + | 📋 + | {{_ 'copyManyCardsPopup-title'}} + unless archived hr ul.pop-over-list li - a.js-more - span.emoji-icon - i.fa.fa-link - | {{_ 'cardMorePopup-title'}} + a.js-archive + | ➡️ + | 📦 + | {{_ 'archive-card'}} + hr + ul.pop-over-list + li + a.js-more + | 🔗 + | {{_ 'cardMorePopup-title'}} template(name="exportCardPopup") ul.pop-over-list li - a(href="{{exportUrlCardPDF}}", download="{{exportFilenameCardPDF}}") - i.fa.fa-upload + a(href="{{exportUrlCardPDF}}",, download="{{exportFilenameCardPDF}}") + | 📤 | {{_ 'export-card-pdf'}} template(name="moveCardPopup") - unless currentUser.isWorker - label {{_ 'boards'}}: - select.js-select-boards(autofocus) - each boards - option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}} - - label {{_ 'swimlanes'}}: - select.js-select-swimlanes - each swimlanes - option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{isTitleDefault title}} - - label {{_ 'lists'}}: - select.js-select-lists - each lists - option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}} - - label {{_ 'cards'}}: - select.js-select-cards - each cards - option(value="{{_id}}" selected="{{#if isDialogOptionCardId _id}}selected{{/if}}") {{add @index 1}}. {{title}} - - div - input(type="radio" name="position" value="above" checked id="position-above" style="display: inline") - label(for="position-above") {{_ 'above-selected-card'}} - div - input(type="radio" name="position" value="below" id="position-below" style="display: inline") - label(for="position-below") {{_ 'below-selected-card'}} - - .edit-controls.clearfix - button.primary.confirm.js-done {{_ 'done'}} + +copyAndMoveCard template(name="copyCardPopup") label(for='copy-card-title') {{_ 'title'}}: textarea#copy-card-title.minicard-composer-textarea.js-card-title(autofocus) = getTitle - unless currentUser.isWorker - label {{_ 'boards'}}: - select.js-select-boards(autofocus) - each boards - option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}} - - label {{_ 'swimlanes'}}: - select.js-select-swimlanes - each swimlanes - option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{isTitleDefault title}} - - label {{_ 'lists'}}: - select.js-select-lists - each lists - option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}} - - label {{_ 'cards'}}: - select.js-select-cards - each cards - option(value="{{_id}}" selected="{{#if isDialogOptionCardId _id}}selected{{/if}}") {{add @index 1}}. {{title}} - - div - input(type="radio" name="position" value="above" checked id="position-above" style="display: inline") - label(for="position-above") {{_ 'above-selected-card'}} - div - input(type="radio" name="position" value="below" id="position-below" style="display: inline") - label(for="position-below") {{_ 'below-selected-card'}} - - .edit-controls.clearfix - button.primary.confirm.js-done {{_ 'done'}} + +copyAndMoveCard template(name="copyManyCardsPopup") label(for='copy-checklist-cards-title') {{_ 'copyManyCardsPopup-instructions'}}: textarea#copy-card-title.minicard-composer-textarea.js-card-title(autofocus) | {{_ 'copyManyCardsPopup-format'}} - unless currentUser.isWorker - label {{_ 'boards'}}: - select.js-select-boards(autofocus) - each boards - option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}} - - label {{_ 'swimlanes'}}: - select.js-select-swimlanes - each swimlanes - option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{isTitleDefault title}} - - label {{_ 'lists'}}: - select.js-select-lists - each lists - option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}} - - label {{_ 'cards'}}: - select.js-select-cards - each cards - option(value="{{_id}}" selected="{{#if isDialogOptionCardId _id}}selected{{/if}}") {{add @index 1}}. {{title}} - - div - input(type="radio" name="position" value="above" checked id="position-above" style="display: inline") - label(for="position-above") {{_ 'above-selected-card'}} - div - input(type="radio" name="position" value="below" id="position-below" style="display: inline") - label(for="position-below") {{_ 'below-selected-card'}} - - .edit-controls.clearfix - button.primary.confirm.js-done {{_ 'done'}} + +copyAndMoveCard template(name="convertChecklistItemToCardPopup") label(for='convert-checklist-item-to-card-title') {{_ 'title'}}: textarea#copy-card-title.minicard-composer-textarea.js-card-title(autofocus) = item.title + +copyAndMoveCard + +template(name="copyAndMoveCard") unless currentUser.isWorker label {{_ 'boards'}}: select.js-select-boards(autofocus) each boards - option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}} + option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{title}} label {{_ 'swimlanes'}}: select.js-select-swimlanes each swimlanes - option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{isTitleDefault title}} + option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{title}} label {{_ 'lists'}}: select.js-select-lists each lists - option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}} - - label {{_ 'cards'}}: - select.js-select-cards - each cards - option(value="{{_id}}" selected="{{#if isDialogOptionCardId _id}}selected{{/if}}") {{add @index 1}}. {{title}} - - div - input(type="radio" name="position" value="above" checked id="position-above" style="display: inline") - label(for="position-above") {{_ 'above-selected-card'}} - div - input(type="radio" name="position" value="below" id="position-below" style="display: inline") - label(for="position-below") {{_ 'below-selected-card'}} + option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{title}} .edit-controls.clearfix button.primary.confirm.js-done {{_ 'done'}} @@ -991,13 +824,13 @@ template(name="cardMembersPopup") each members li.item(class="{{#if isCardMember}}active{{/if}}") a.name.js-select-member(href="#") - +userAvatar(userId=userId) + +userAvatar(userId=user._id) span.full-name - = userData.profile.fullname - if userData.username - | (#{userData.username}) + = user.profile.fullname + | ({{ user.username }}) if isCardMember - i.fa.fa-check + | ✅ + template(name="cardAssigneesPopup") input.card-assignees-filter(type="text" placeholder="{{_ 'search'}}") unless currentUser.isWorker @@ -1005,13 +838,12 @@ template(name="cardAssigneesPopup") each members li.item(class="{{#if isCardAssignee}}active{{/if}}") a.name.js-select-assignee(href="#") - +userAvatar(userId=userId) + +userAvatar(userId=user._id) span.full-name - = userData.profile.fullname - if userData.username - | (#{userData.username}) + = user.profile.fullname + | ({{ user.username }}) if isCardAssignee - i.fa.fa-check + | ✅ if currentUser.isWorker ul.pop-over-list.js-card-assignee-list li.item(class="{{#if currentUser.isCardAssignee}}active{{/if}}") @@ -1019,10 +851,10 @@ template(name="cardAssigneesPopup") +userAvatar(userId=currentUser._id) span.full-name = currentUser.profile.fullname - if currentUser.username - | (#{currentUser.username}) + | ({{ currentUser.username }}) if currentUser.isCardAssignee - i.fa.fa-check + | ✅ + template(name="cardAssigneePopup") .board-assignee-menu .mini-profile-info @@ -1045,7 +877,7 @@ template(name="cardMorePopup") span.clearfix span {{_ 'link-card'}} = ' ' - i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}") + | {{#if board.isPublic}}🌐{{else}}🔒{{/if}} input.inline-input(type="text" id="cardURL" readonly value="{{ originRelativeUrl }}" autofocus="autofocus") button.js-copy-card-link-to-clipboard(class="btn" id="clipboard") {{_ 'copy-card-link-to-clipboard'}} .copied-tooltip {{_ 'copied'}} @@ -1077,20 +909,19 @@ template(name="cardMorePopup") option(value="{{_id}}") {{title}} br | {{_ 'added'}} - span.date(title=card.createdAt) {{ displayDate createdAt 'LLL' }} + span.date(title=card.createdAt) {{ moment createdAt 'LLL' }} if currentUser.isBoardAdmin a.js-delete(title="{{_ 'card-delete-notice'}}") {{_ 'delete'}} template(name="setCardColorPopup") form.edit-label - .palette-colors - each colors - unless $eq color 'white' - span.card-label.palette-color.js-palette-color(class="card-details-{{color}}") - if(isSelected color) - i.fa.fa-check - button.primary.confirm.js-submit {{_ 'save'}} - button.js-remove-color.negate.wide.right {{_ 'unset-color'}} + .palette-colors: each colors + unless $eq color 'white' + span.card-label.palette-color.js-palette-color(class="card-details-{{color}}") + if(isSelected color) + | ✅ + button.primary.confirm.js-submit {{_ 'save'}} + button.js-remove-color.negate.wide.right {{_ 'unset-color'}} template(name="cardDeletePopup") p {{_ "card-delete-pop"}} @@ -1122,12 +953,12 @@ template(name="cardStartVotingPopup") .materialCheckBox#vote-public(name="vote-public" class="{{#if votePublic}}is-checked{{/if}}") span {{_ 'vote-public'}} .check-div.flex - i.fa.fa-clock-o + | ⏰ a.js-end-date span | {{_ 'card-end'}} unless getVoteEnd - i.fa.fa-plus + | ➕ if getVoteEnd +voteEndDate @@ -1168,12 +999,12 @@ template(name="cardStartPlanningPokerPopup") .materialCheckBox#poker-allow-non-members(name="poker-allow-non-members" class="{{#if pokerAllowNonBoardMembers}}is-checked{{/if}}") span {{_ 'allowNonBoardMembers'}} .check-div.flex - i.fa.fa-clock-o + | ⏰ a.js-end-date span | {{_ 'card-end'}} unless getPokerEnd - i.fa.fa-plus + | ➕ if getPokerEnd +pokerEndDate diff --git a/client/components/cards/cardDetails.js b/client/components/cards/cardDetails.js index 946d660f2..43ee28473 100644 --- a/client/components/cards/cardDetails.js +++ b/client/components/cards/cardDetails.js @@ -1,31 +1,25 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; -import { - setupDatePicker, - datePickerRendered, - datePickerHelpers, - datePickerEvents, -} from '/client/lib/datepicker'; -import { - formatDateTime, - formatDate, - formatTime, - getISOWeek, - isValidDate, - isBefore, - isAfter, - isSame, - add, - subtract, - startOf, - endOf, - format, - parseDate, - now, - createDate, - fromNow, - calendar +import { DatePicker } from '/client/lib/datepicker'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar } from '/imports/lib/dateUtils'; import Cards from '/models/cards'; import Boards from '/models/boards'; @@ -36,222 +30,57 @@ import Lists from '/models/lists'; import CardComments from '/models/cardComments'; import { ALLOWED_COLORS } from '/config/const'; import { UserAvatar } from '../users/userAvatar'; -import { BoardSwimlaneListCardDialog } from '/client/lib/dialogWithBoardSwimlaneListCard'; +import { DialogWithBoardSwimlaneList } from '/client/lib/dialogWithBoardSwimlaneList'; import { handleFileUpload } from './attachments'; -import { InfiniteScrolling } from '/client/lib/infiniteScrolling'; -import { - getCurrentCardIdFromContext, - getCurrentCardFromContext, -} from '/client/lib/currentCard'; import uploadProgressManager from '../../lib/uploadProgressManager'; const subManager = new SubsManager(); const { calculateIndexData } = Utils; -function getCardId() { - return getCurrentCardIdFromContext(); -} +BlazeComponent.extendComponent({ + mixins() { + return [Mixins.InfiniteScrolling]; + }, -function getBoardBodyInstance() { - const boardBodyEl = document.querySelector('.board-body'); - if (boardBodyEl) { - const view = Blaze.getView(boardBodyEl); - if (view && view.templateInstance) return view.templateInstance(); - } - return null; -} - -function getCardDetailsElement(cardId) { - if (!cardId) { - return null; - } - - const cardDetailsElements = document.querySelectorAll('.js-card-details'); - for (const element of cardDetailsElements) { - if (Blaze.getData(element)?._id === cardId) { - return element; - } - } - - return null; -} - -Template.cardDetails.onCreated(function () { - this.currentBoard = Utils.getCurrentBoard(); - this.isLoaded = new ReactiveVar(false); - this.infiniteScrolling = new InfiniteScrolling(); - - const boardBody = getBoardBodyInstance(); - if (boardBody !== null) { - // Only show overlay in mobile mode, not in desktop mode - const isMobile = Utils.getMobileMode(); - if (isMobile) { - boardBody.showOverlay.set(true); - } - boardBody.mouseHasEnterCardDetails = false; - } - - this.calculateNextPeak = () => { + calculateNextPeak() { const cardElement = this.find('.js-card-details'); if (cardElement) { const altitude = cardElement.scrollHeight; - this.infiniteScrolling.setNextPeak(altitude); + this.callFirstWith(this, 'setNextPeak', altitude); } - }; + }, - this.reachNextPeak = () => { - const activitiesEl = this.find('.activities'); - if (activitiesEl) { - const view = Blaze.getView(activitiesEl); - if (view && view.templateInstance) { - const activitiesTpl = view.templateInstance(); - if (activitiesTpl && activitiesTpl.loadNextPage) { - activitiesTpl.loadNextPage(); - } + reachNextPeak() { + const activitiesComponent = this.childComponents('activities')[0]; + activitiesComponent.loadNextPage(); + }, + + onCreated() { + this.currentBoard = Utils.getCurrentBoard(); + this.isLoaded = new ReactiveVar(false); + + if (this.parentComponent() && this.parentComponent().parentComponent()) { + const boardBody = this.parentComponent().parentComponent(); + //in Miniview parent is Board, not BoardBody. + if (boardBody !== null) { + boardBody.showOverlay.set(true); + boardBody.mouseHasEnterCardDetails = false; } } - }; + this.calculateNextPeak(); - Meteor.subscribe('unsaved-edits'); -}); + Meteor.subscribe('unsaved-edits'); -Template.cardDetails.onRendered(function () { - this.calculateNextPeak(); - if (Meteor.settings.public.CARD_OPENED_WEBHOOK_ENABLED) { - // Send Webhook but not create Activities records --- - const card = Template.currentData(); - const userId = Meteor.userId(); - const params = { - userId, - cardId: card._id, - boardId: card.boardId, - listId: card.listId, - user: ReactiveCache.getCurrentUser().username, - url: '', - }; + // this.findUsersOptions = new ReactiveVar({}); + // this.page = new ReactiveVar(1); + // this.autorun(() => { + // const limitUsers = this.page.get() * Number.MAX_SAFE_INTEGER; + // this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => {}); + // }); + }, - const integrations = ReactiveCache.getIntegrations({ - boardId: { $in: [card.boardId, Integrations.Const.GLOBAL_WEBHOOK_ID] }, - enabled: true, - activities: { $in: ['CardDetailsRendered', 'all'] }, - }); - - if (integrations.length > 0) { - integrations.forEach((integration) => { - Meteor.call( - 'outgoingWebhooks', - integration, - 'CardSelected', - params, - () => { }, - ); - }); - } - //------------- - } - - const $checklistsDom = this.$('.card-checklist-items'); - - $checklistsDom.sortable({ - tolerance: 'pointer', - helper: 'clone', - handle: '.checklist-title', - items: '.js-checklist', - placeholder: 'checklist placeholder', - distance: 7, - start(evt, ui) { - ui.placeholder.height(ui.helper.height()); - EscapeActions.clickExecute(evt.target, 'inlinedForm'); - }, - stop(evt, ui) { - let prevChecklist = ui.item.prev('.js-checklist').get(0); - if (prevChecklist) { - prevChecklist = Blaze.getData(prevChecklist).checklist; - } - let nextChecklist = ui.item.next('.js-checklist').get(0); - if (nextChecklist) { - nextChecklist = Blaze.getData(nextChecklist).checklist; - } - const sortIndex = calculateIndexData(prevChecklist, nextChecklist, 1); - - $checklistsDom.sortable('cancel'); - const checklist = Blaze.getData(ui.item.get(0)).checklist; - - Checklists.update(checklist._id, { - $set: { - sort: sortIndex.base, - }, - }); - }, - }); - - const $subtasksDom = this.$('.card-subtasks-items'); - - $subtasksDom.sortable({ - tolerance: 'pointer', - helper: 'clone', - handle: '.subtask-title', - items: '.js-subtasks', - placeholder: 'subtasks placeholder', - distance: 7, - start(evt, ui) { - ui.placeholder.height(ui.helper.height()); - EscapeActions.executeUpTo('popup-close'); - }, - stop(evt, ui) { - let prevChecklist = ui.item.prev('.js-subtasks').get(0); - if (prevChecklist) { - prevChecklist = Blaze.getData(prevChecklist).subtask; - } - let nextChecklist = ui.item.next('.js-subtasks').get(0); - if (nextChecklist) { - nextChecklist = Blaze.getData(nextChecklist).subtask; - } - const sortIndex = calculateIndexData(prevChecklist, nextChecklist, 1); - - $subtasksDom.sortable('cancel'); - const subtask = Blaze.getData(ui.item.get(0)).subtask; - - Subtasks.update(subtask._id, { - $set: { - subtaskSort: sortIndex.base, - }, - }); - }, - }); - - function userIsMember() { - return ReactiveCache.getCurrentUser()?.isBoardMember(); - } - - // Disable sorting if the current user is not a board member - this.autorun(() => { - const disabled = !userIsMember(); - if ( - $checklistsDom.data('uiSortable') || - $checklistsDom.data('sortable') - ) { - $checklistsDom.sortable('option', 'disabled', disabled); - if (Utils.isTouchScreenOrShowDesktopDragHandles()) { - $checklistsDom.sortable({ handle: '.checklist-handle' }); - } - } - if ($subtasksDom.data('uiSortable') || $subtasksDom.data('sortable')) { - $subtasksDom.sortable('option', 'disabled', disabled); - } - }); -}); - -Template.cardDetails.onDestroyed(function () { - const boardBody = getBoardBodyInstance(); - if (boardBody === null) return; - boardBody.showOverlay.set(false); -}); - -Template.cardDetails.helpers({ isWatching() { - const card = Template.currentData(); - if (!card || typeof card.findWatcher !== 'function') return false; + const card = this.currentData(); return card.findWatcher(Meteor.userId()); }, @@ -259,30 +88,13 @@ Template.cardDetails.helpers({ return ReactiveCache.getCurrentUser().hasCustomFieldsGrid(); }, + cardMaximized() { return !Utils.getPopupCardId() && ReactiveCache.getCurrentUser().hasCardMaximized(); }, - showActivities() { - const user = ReactiveCache.getCurrentUser(); - return user && user.hasShowActivities(); - }, - - cardCollapsed() { - const user = ReactiveCache.getCurrentUser(); - if (user && user.profile) { - return !!user.profile.cardCollapsed; - } - if (Users.getPublicCardCollapsed) { - const stored = Users.getPublicCardCollapsed(); - if (typeof stored === 'boolean') return stored; - } - return false; - }, - presentParentTask() { - const tpl = Template.instance(); - let result = tpl.currentBoard.presentParentTask; + let result = this.currentBoard.presentParentTask; if (result === null || result === undefined) { result = 'no-parent'; } @@ -290,7 +102,7 @@ Template.cardDetails.helpers({ }, linkForCard() { - const card = Template.currentData(); + const card = this.currentData(); let result = '#'; if (card) { const board = ReactiveCache.getBoard(card.boardId); @@ -306,7 +118,7 @@ Template.cardDetails.helpers({ }, showVotingButtons() { - const card = Template.currentData(); + const card = this.currentData(); return ( (currentUser.isBoardMember() || (currentUser && card.voteAllowNonBoardMembers())) && @@ -315,7 +127,7 @@ Template.cardDetails.helpers({ }, showPlanningPokerButtons() { - const card = Template.currentData(); + const card = this.currentData(); return ( (currentUser.isBoardMember() || (currentUser && card.pokerAllowNonBoardMembers())) && @@ -328,424 +140,433 @@ Template.cardDetails.helpers({ return user && user.isVerticalScrollbars(); }, + /** returns if the list id is the current list id + * @param listId list id to check + * @return is the list id the current list id ? + */ isCurrentListId(listId) { - const data = Template.currentData(); - if (!data || typeof data.listId === 'undefined') return false; - return data.listId == listId; + const ret = this.data().listId == listId; + return ret; }, - isLoaded() { - return Template.instance().isLoaded; - }, -}); + onRendered() { + if (Meteor.settings.public.CARD_OPENED_WEBHOOK_ENABLED) { + // Send Webhook but not create Activities records --- + const card = this.currentData(); + const userId = Meteor.userId(); + const params = { + userId, + cardId: card._id, + boardId: card.boardId, + listId: card.listId, + user: ReactiveCache.getCurrentUser().username, + url: '', + }; -Template.cardDetails.events({ - [`${CSSEvents.transitionend} .js-card-details`](event, tpl) { - tpl.isLoaded.set(true); - }, - [`${CSSEvents.animationend} .js-card-details`](event, tpl) { - tpl.isLoaded.set(true); - }, - 'scroll .js-card-details'(event, tpl) { - tpl.infiniteScrolling.checkScrollPosition(event.currentTarget, () => { - tpl.reachNextPeak(); - }); - }, - 'click .js-card-collapse-toggle'(event, tpl) { - const user = ReactiveCache.getCurrentUser(); - const currentState = user && user.profile ? !!user.profile.cardCollapsed : !!Users.getPublicCardCollapsed(); - if (user) { - Meteor.call('setCardCollapsed', !currentState); - } else if (Users.setPublicCardCollapsed) { - Users.setPublicCardCollapsed(!currentState); - } - }, - 'mousedown .js-card-drag-handle'(event) { - event.preventDefault(); - const $card = $(event.target).closest('.card-details'); - const startX = event.clientX; - const startY = event.clientY; - const startLeft = $card.offset().left; - const startTop = $card.offset().top; - - const onMouseMove = (e) => { - const deltaX = e.clientX - startX; - const deltaY = e.clientY - startY; - $card.css({ - left: startLeft + deltaX + 'px', - top: startTop + deltaY + 'px' + const integrations = ReactiveCache.getIntegrations({ + boardId: { $in: [card.boardId, Integrations.Const.GLOBAL_WEBHOOK_ID] }, + enabled: true, + activities: { $in: ['CardDetailsRendered', 'all'] }, }); - }; - const onMouseUp = () => { - $(document).off('mousemove', onMouseMove); - $(document).off('mouseup', onMouseUp); - }; - - $(document).on('mousemove', onMouseMove); - $(document).on('mouseup', onMouseUp); - }, - 'mousedown .js-card-title-drag-handle'(event) { - // Allow dragging from title for ReadOnly users - // Don't interfere with text selection - if (event.target.tagName === 'A' || $(event.target).closest('a').length > 0) { - return; // Don't drag if clicking on links - } - - event.preventDefault(); - const $card = $(event.target).closest('.card-details'); - const startX = event.clientX; - const startY = event.clientY; - const startLeft = $card.offset().left; - const startTop = $card.offset().top; - - const onMouseMove = (e) => { - const deltaX = e.clientX - startX; - const deltaY = e.clientY - startY; - $card.css({ - left: startLeft + deltaX + 'px', - top: startTop + deltaY + 'px' - }); - }; - - const onMouseUp = () => { - $(document).off('mousemove', onMouseMove); - $(document).off('mouseup', onMouseUp); - }; - - $(document).on('mousemove', onMouseMove); - $(document).on('mouseup', onMouseUp); - }, - 'click .js-close-card-details'(event, tpl) { - // Get board ID from either the card data or current board in session - const card = Template.currentData(); - const boardId = (card && card.boardId) || Utils.getCurrentBoard()._id; - const cardId = card && card._id; - - if (boardId) { - // In desktop mode, remove from openCards array - const isMobile = Utils.getMobileMode(); - if (!isMobile && cardId) { - const openCards = Session.get('openCards') || []; - const filtered = openCards.filter(id => id !== cardId); - Session.set('openCards', filtered); - - // If this was the current card, clear it - if (Session.get('currentCard') === cardId) { - Session.set('currentCard', null); - } - // Don't navigate away in desktop mode - just close the card - return; - } - - // Mobile mode: Clear the current card session to close the card - Session.set('currentCard', null); - - // Navigate back to board without card - const board = ReactiveCache.getBoard(boardId); - if (board) { - FlowRouter.go('board', { - id: board._id, - slug: board.slug, + if (integrations.length > 0) { + integrations.forEach((integration) => { + Meteor.call( + 'outgoingWebhooks', + integration, + 'CardSelected', + params, + () => { }, + ); }); } + //------------- } - }, - 'click .js-copy-link'(event, tpl) { - event.preventDefault(); - const card = Template.currentData(); - const url = card.absoluteUrl(); - const promise = Utils.copyTextToClipboard(url); - const $tooltip = tpl.$('.card-details-header .copied-tooltip'); - Utils.showCopied(promise, $tooltip); - }, - 'change .js-date-format-selector'(event) { - const dateFormat = event.target.value; - Meteor.call('changeDateFormat', dateFormat); - }, - 'click .js-open-card-details-menu': Popup.open('cardDetailsActions'), - // Mobile: switch to desktop popup view (maximize) - 'click .js-mobile-switch-to-desktop'(event) { - event.preventDefault(); - // Switch global mode to desktop so the card appears as desktop popup - Utils.setMobileMode(false); - }, - 'click .js-card-zoom-in'(event) { - event.preventDefault(); - const current = Utils.getCardZoom(); - const newZoom = Math.min(3.0, current + 0.1); - Utils.setCardZoom(newZoom); - }, - 'click .js-card-zoom-out'(event) { - event.preventDefault(); - const current = Utils.getCardZoom(); - const newZoom = Math.max(0.5, current - 0.1); - Utils.setCardZoom(newZoom); - }, - 'click .js-card-mobile-desktop-toggle'(event) { - event.preventDefault(); - const currentMode = Utils.getMobileMode(); - Utils.setMobileMode(!currentMode); - }, - async 'submit .js-card-description'(event, tpl) { - event.preventDefault(); - const description = tpl.find('.js-new-description-input').value; - const card = Template.currentData(); - await card.setDescription(description); - }, - async 'submit .js-card-details-title'(event, tpl) { - event.preventDefault(); - const titleInput = tpl.find('.js-edit-card-title'); - const title = titleInput ? titleInput.value.trim() : ''; - const card = Template.currentData(); - if (title) { - await card.setTitle(title); - } else { - await card.setTitle(''); - } - }, - 'submit .js-card-details-assigner'(event, tpl) { - event.preventDefault(); - const assignerInput = tpl.find('.js-edit-card-assigner'); - const assigner = assignerInput ? assignerInput.value.trim() : ''; - const card = Template.currentData(); - if (assigner) { - card.setAssignedBy(assigner); - } else { - card.setAssignedBy(''); - } - }, - 'submit .js-card-details-requester'(event, tpl) { - event.preventDefault(); - const requesterInput = tpl.find('.js-edit-card-requester'); - const requester = requesterInput ? requesterInput.value.trim() : ''; - const card = Template.currentData(); - if (requester) { - card.setRequestedBy(requester); - } else { - card.setRequestedBy(''); - } - }, - 'keydown input.js-edit-card-sort'(evt, tpl) { - // enter = save - if (evt.keyCode === 13) { - tpl.find('button[type=submit]').click(); - } - }, - async 'submit .js-card-details-sort'(event, tpl) { - event.preventDefault(); - const sortInput = tpl.find('.js-edit-card-sort'); - const sort = parseFloat(sortInput ? sortInput.value.trim() : ''); - if (!Number.isNaN(sort)) { - let card = Template.currentData(); - await card.move(card.boardId, card.swimlaneId, card.listId, sort); - } - }, - async 'change .js-select-card-details-lists'(event, tpl) { - let card = Template.currentData(); - const listSelect = tpl.$('.js-select-card-details-lists')[0]; - const listId = listSelect.options[listSelect.selectedIndex].value; + const $checklistsDom = this.$('.card-checklist-items'); - const minOrder = card.getMinSort(listId, card.swimlaneId); - await card.move(card.boardId, card.swimlaneId, listId, minOrder - 1); - }, - 'click .js-go-to-linked-card'() { - const card = Template.currentData(); - Utils.goCardId(card.linkedId); - }, - 'click .js-member': Popup.open('cardMember'), - 'click .js-add-members': Popup.open('cardMembers'), - 'click .js-assignee': Popup.open('cardAssignee'), - 'click .js-add-assignees': Popup.open('cardAssignees'), - 'click .js-add-labels': Popup.open('cardLabels'), - 'click .js-received-date': Popup.open('editCardReceivedDate'), - 'click .js-start-date': Popup.open('editCardStartDate'), - 'click .js-due-date': Popup.open('editCardDueDate'), - 'click .js-end-date': Popup.open('editCardEndDate'), - 'click .js-show-positive-votes': Popup.open('positiveVoteMembers'), - 'click .js-show-negative-votes': Popup.open('negativeVoteMembers'), - 'click .js-custom-fields': Popup.open('cardCustomFields'), - 'mouseenter .js-card-details'(event, tpl) { - const boardBody = getBoardBodyInstance(tpl); - if (boardBody === null) return; - boardBody.showOverlay.set(true); - boardBody.mouseHasEnterCardDetails = true; - }, - 'mousedown .js-card-details'() { - Session.set('cardDetailsIsDragging', false); - Session.set('cardDetailsIsMouseDown', true); - }, - 'mousemove .js-card-details'() { - if (Session.get('cardDetailsIsMouseDown')) { - Session.set('cardDetailsIsDragging', true); - } - }, - 'mouseup .js-card-details'() { - Session.set('cardDetailsIsDragging', false); - Session.set('cardDetailsIsMouseDown', false); - }, - async 'click #toggleHideCheckedChecklistItems'() { - const card = Template.currentData(); - await card.toggleHideCheckedChecklistItems(); - }, - 'click #toggleCustomFieldsGridButton'() { - Meteor.call('toggleCustomFieldsGrid'); - }, - 'click .js-maximize-card-details'() { - Meteor.call('toggleCardMaximized'); - autosize($('.card-details')); - }, - 'click .js-minimize-card-details'() { - Meteor.call('toggleCardMaximized'); - autosize($('.card-details')); - }, - 'click .js-vote'(e) { - const card = Template.currentData(); - const forIt = $(e.target).hasClass('js-vote-positive'); - let newState = null; - if ( - card.voteState() === null || - (card.voteState() === false && forIt) || - (card.voteState() === true && !forIt) - ) { - newState = forIt; - } - // Use secure server method; direct client updates to vote are blocked - Meteor.call('cards.vote', card._id, newState); - }, - 'click .js-poker'(e) { - const card = Template.currentData(); - let newState = null; - if ($(e.target).hasClass('js-poker-vote-one')) { - newState = 'one'; - Meteor.call('cards.pokerVote', card._id, newState); - } - if ($(e.target).hasClass('js-poker-vote-two')) { - newState = 'two'; - Meteor.call('cards.pokerVote', card._id, newState); - } - if ($(e.target).hasClass('js-poker-vote-three')) { - newState = 'three'; - Meteor.call('cards.pokerVote', card._id, newState); - } - if ($(e.target).hasClass('js-poker-vote-five')) { - newState = 'five'; - Meteor.call('cards.pokerVote', card._id, newState); - } - if ($(e.target).hasClass('js-poker-vote-eight')) { - newState = 'eight'; - Meteor.call('cards.pokerVote', card._id, newState); - } - if ($(e.target).hasClass('js-poker-vote-thirteen')) { - newState = 'thirteen'; - Meteor.call('cards.pokerVote', card._id, newState); - } - if ($(e.target).hasClass('js-poker-vote-twenty')) { - newState = 'twenty'; - Meteor.call('cards.pokerVote', card._id, newState); - } - if ($(e.target).hasClass('js-poker-vote-forty')) { - newState = 'forty'; - Meteor.call('cards.pokerVote', card._id, newState); - } - if ($(e.target).hasClass('js-poker-vote-one-hundred')) { - newState = 'oneHundred'; - Meteor.call('cards.pokerVote', card._id, newState); - } - if ($(e.target).hasClass('js-poker-vote-unsure')) { - newState = 'unsure'; - Meteor.call('cards.pokerVote', card._id, newState); - } - }, - 'click .js-poker-finish'(e) { - if ($(e.target).hasClass('js-poker-finish')) { - e.preventDefault(); - const card = Template.currentData(); - const now = new Date(); - Meteor.call('cards.setPokerEnd', card._id, now); - } - }, - 'click .js-poker-replay'(e) { - if ($(e.target).hasClass('js-poker-replay')) { - e.preventDefault(); - const currentCard = Template.currentData(); - Meteor.call('cards.replayPoker', currentCard._id); - Meteor.call('cards.unsetPokerEnd', currentCard._id); - Meteor.call('cards.unsetPokerEstimation', currentCard._id); - } - }, - 'click .js-poker-estimation'(event, tpl) { - event.preventDefault(); - const card = Template.currentData(); - const ruleTitle = tpl.find('#pokerEstimation').value; - if (ruleTitle !== undefined && ruleTitle !== '') { - tpl.find('#pokerEstimation').value = ''; + $checklistsDom.sortable({ + tolerance: 'pointer', + helper: 'clone', + handle: '.checklist-title', + items: '.js-checklist', + placeholder: 'checklist placeholder', + distance: 7, + start(evt, ui) { + ui.placeholder.height(ui.helper.height()); + EscapeActions.clickExecute(evt.target, 'inlinedForm'); + }, + stop(evt, ui) { + let prevChecklist = ui.item.prev('.js-checklist').get(0); + if (prevChecklist) { + prevChecklist = Blaze.getData(prevChecklist).checklist; + } + let nextChecklist = ui.item.next('.js-checklist').get(0); + if (nextChecklist) { + nextChecklist = Blaze.getData(nextChecklist).checklist; + } + const sortIndex = calculateIndexData(prevChecklist, nextChecklist, 1); - if (ruleTitle) { - Meteor.call('cards.setPokerEstimation', card._id, parseInt(ruleTitle, 10)); - } else { - Meteor.call('cards.unsetPokerEstimation', card._id); + $checklistsDom.sortable('cancel'); + const checklist = Blaze.getData(ui.item.get(0)).checklist; + + Checklists.update(checklist._id, { + $set: { + sort: sortIndex.base, + }, + }); + }, + }); + + const $subtasksDom = this.$('.card-subtasks-items'); + + $subtasksDom.sortable({ + tolerance: 'pointer', + helper: 'clone', + handle: '.subtask-title', + items: '.js-subtasks', + placeholder: 'subtasks placeholder', + distance: 7, + start(evt, ui) { + ui.placeholder.height(ui.helper.height()); + EscapeActions.executeUpTo('popup-close'); + }, + stop(evt, ui) { + let prevChecklist = ui.item.prev('.js-subtasks').get(0); + if (prevChecklist) { + prevChecklist = Blaze.getData(prevChecklist).subtask; + } + let nextChecklist = ui.item.next('.js-subtasks').get(0); + if (nextChecklist) { + nextChecklist = Blaze.getData(nextChecklist).subtask; + } + const sortIndex = calculateIndexData(prevChecklist, nextChecklist, 1); + + $subtasksDom.sortable('cancel'); + const subtask = Blaze.getData(ui.item.get(0)).subtask; + + Subtasks.update(subtask._id, { + $set: { + subtaskSort: sortIndex.base, + }, + }); + }, + }); + + function userIsMember() { + return ReactiveCache.getCurrentUser()?.isBoardMember(); + } + + // Disable sorting if the current user is not a board member + this.autorun(() => { + const disabled = !userIsMember(); + if ( + $checklistsDom.data('uiSortable') || + $checklistsDom.data('sortable') + ) { + $checklistsDom.sortable('option', 'disabled', disabled); + if (Utils.isTouchScreenOrShowDesktopDragHandles()) { + $checklistsDom.sortable({ handle: '.checklist-handle' }); + } } - } - }, - // Drag and drop file upload handlers - 'dragover .js-card-details'(event) { - // Only prevent default for file drags to avoid interfering with other drag operations - const dataTransfer = event.originalEvent.dataTransfer; - if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { - event.preventDefault(); - event.stopPropagation(); - } - }, - 'dragenter .js-card-details'(event) { - const dataTransfer = event.originalEvent.dataTransfer; - if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { - event.preventDefault(); - event.stopPropagation(); - const card = Template.currentData(); - const board = card.board(); - // Only allow drag-and-drop if user can modify card and board allows attachments - if (Utils.canModifyCard() && board && board.allowsAttachments) { - $(event.currentTarget).addClass('is-dragging-over'); + if ($subtasksDom.data('uiSortable') || $subtasksDom.data('sortable')) { + $subtasksDom.sortable('option', 'disabled', disabled); } - } + }); }, - 'dragleave .js-card-details'(event) { - const dataTransfer = event.originalEvent.dataTransfer; - if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { - event.preventDefault(); - event.stopPropagation(); - $(event.currentTarget).removeClass('is-dragging-over'); - } + + onDestroyed() { + if (this.parentComponent() === null) return; + const parentComponent = this.parentComponent().parentComponent(); + //on mobile view parent is Board, not board body. + if (parentComponent === null) return; + parentComponent.showOverlay.set(false); }, - 'drop .js-card-details'(event) { - const dataTransfer = event.originalEvent.dataTransfer; - if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { - event.preventDefault(); - event.stopPropagation(); - $(event.currentTarget).removeClass('is-dragging-over'); - const card = Template.currentData(); - const board = card.board(); + events() { + const events = { + [`${CSSEvents.transitionend} .js-card-details`]() { + this.isLoaded.set(true); + }, + [`${CSSEvents.animationend} .js-card-details`]() { + this.isLoaded.set(true); + }, + }; - // Check permissions - if (!Utils.canModifyCard() || !board || !board.allowsAttachments) { - return; - } + return [ + { + ...events, + 'click .js-close-card-details'() { + Utils.goBoardId(this.data().boardId); + }, + 'click .js-copy-link'(event) { + event.preventDefault(); + const promise = Utils.copyTextToClipboard(event.target.href); - // Check if this is a file drop (not a checklist item reorder) - if (!dataTransfer.files || dataTransfer.files.length === 0) { - return; - } + const $tooltip = this.$('.card-details-header .copied-tooltip'); + Utils.showCopied(promise, $tooltip); + }, + 'change .js-date-format-selector'(event) { + const dateFormat = event.target.value; + Meteor.call('changeDateFormat', dateFormat); + }, + 'click .js-open-card-details-menu': Popup.open('cardDetailsActions'), + 'submit .js-card-description'(event) { + event.preventDefault(); + const description = this.currentComponent().getValue(); + this.data().setDescription(description); + }, + 'submit .js-card-details-title'(event) { + event.preventDefault(); + const title = this.currentComponent().getValue().trim(); + if (title) { + this.data().setTitle(title); + } else { + this.data().setTitle(''); + } + }, + 'submit .js-card-details-assigner'(event) { + event.preventDefault(); + const assigner = this.currentComponent().getValue().trim(); + if (assigner) { + this.data().setAssignedBy(assigner); + } else { + this.data().setAssignedBy(''); + } + }, + 'submit .js-card-details-requester'(event) { + event.preventDefault(); + const requester = this.currentComponent().getValue().trim(); + if (requester) { + this.data().setRequestedBy(requester); + } else { + this.data().setRequestedBy(''); + } + }, + 'keydown input.js-edit-card-sort'(evt) { + // enter = save + if (evt.keyCode === 13) { + this.find('button[type=submit]').click(); + } + }, + 'submit .js-card-details-sort'(event) { + event.preventDefault(); + const sort = parseFloat(this.currentComponent() + .getValue() + .trim()); + if (!Number.isNaN(sort)) { + let card = this.data(); + card.move(card.boardId, card.swimlaneId, card.listId, sort); + } + }, + 'change .js-select-card-details-lists'(event) { + let card = this.data(); + const listSelect = this.$('.js-select-card-details-lists')[0]; + const listId = listSelect.options[listSelect.selectedIndex].value; - const files = dataTransfer.files; - if (files && files.length > 0) { - handleFileUpload(card, files); - } - } + const minOrder = card.getMinSort(listId, card.swimlaneId); + card.move(card.boardId, card.swimlaneId, listId, minOrder - 1); + }, + 'click .js-go-to-linked-card'() { + Utils.goCardId(this.data().linkedId); + }, + 'click .js-member': Popup.open('cardMember'), + 'click .js-add-members': Popup.open('cardMembers'), + 'click .js-assignee': Popup.open('cardAssignee'), + 'click .js-add-assignees': Popup.open('cardAssignees'), + 'click .js-add-labels': Popup.open('cardLabels'), + 'click .js-received-date': Popup.open('editCardReceivedDate'), + 'click .js-start-date': Popup.open('editCardStartDate'), + 'click .js-due-date': Popup.open('editCardDueDate'), + 'click .js-end-date': Popup.open('editCardEndDate'), + 'click .js-show-positive-votes': Popup.open('positiveVoteMembers'), + 'click .js-show-negative-votes': Popup.open('negativeVoteMembers'), + 'click .js-custom-fields': Popup.open('cardCustomFields'), + 'mouseenter .js-card-details'() { + if (this.parentComponent() === null) return; + const parentComponent = this.parentComponent().parentComponent(); + //on mobile view parent is Board, not BoardBody. + if (parentComponent === null) return; + parentComponent.showOverlay.set(true); + parentComponent.mouseHasEnterCardDetails = true; + }, + 'mousedown .js-card-details'() { + Session.set('cardDetailsIsDragging', false); + Session.set('cardDetailsIsMouseDown', true); + }, + 'mousemove .js-card-details'() { + if (Session.get('cardDetailsIsMouseDown')) { + Session.set('cardDetailsIsDragging', true); + } + }, + 'mouseup .js-card-details'() { + Session.set('cardDetailsIsDragging', false); + Session.set('cardDetailsIsMouseDown', false); + }, + 'click #toggleShowActivitiesCard'() { + this.data().toggleShowActivities(); + }, + 'click #toggleHideCheckedChecklistItems'() { + this.data().toggleHideCheckedChecklistItems(); + }, + 'click #toggleCustomFieldsGridButton'() { + Meteor.call('toggleCustomFieldsGrid'); + }, + 'click .js-maximize-card-details'() { + Meteor.call('toggleCardMaximized'); + autosize($('.card-details')); + }, + 'click .js-minimize-card-details'() { + Meteor.call('toggleCardMaximized'); + autosize($('.card-details')); + }, + 'click .js-vote'(e) { + const forIt = $(e.target).hasClass('js-vote-positive'); + let newState = null; + if ( + this.data().voteState() === null || + (this.data().voteState() === false && forIt) || + (this.data().voteState() === true && !forIt) + ) { + newState = forIt; + } + // Use secure server method; direct client updates to vote are blocked + Meteor.call('cards.vote', this.data()._id, newState); + }, + 'click .js-poker'(e) { + let newState = null; + if ($(e.target).hasClass('js-poker-vote-one')) { + newState = 'one'; + Meteor.call('cards.pokerVote', this.data()._id, newState); + } + if ($(e.target).hasClass('js-poker-vote-two')) { + newState = 'two'; + Meteor.call('cards.pokerVote', this.data()._id, newState); + } + if ($(e.target).hasClass('js-poker-vote-three')) { + newState = 'three'; + Meteor.call('cards.pokerVote', this.data()._id, newState); + } + if ($(e.target).hasClass('js-poker-vote-five')) { + newState = 'five'; + Meteor.call('cards.pokerVote', this.data()._id, newState); + } + if ($(e.target).hasClass('js-poker-vote-eight')) { + newState = 'eight'; + Meteor.call('cards.pokerVote', this.data()._id, newState); + } + if ($(e.target).hasClass('js-poker-vote-thirteen')) { + newState = 'thirteen'; + Meteor.call('cards.pokerVote', this.data()._id, newState); + } + if ($(e.target).hasClass('js-poker-vote-twenty')) { + newState = 'twenty'; + Meteor.call('cards.pokerVote', this.data()._id, newState); + } + if ($(e.target).hasClass('js-poker-vote-forty')) { + newState = 'forty'; + Meteor.call('cards.pokerVote', this.data()._id, newState); + } + if ($(e.target).hasClass('js-poker-vote-one-hundred')) { + newState = 'oneHundred'; + Meteor.call('cards.pokerVote', this.data()._id, newState); + } + if ($(e.target).hasClass('js-poker-vote-unsure')) { + newState = 'unsure'; + Meteor.call('cards.pokerVote', this.data()._id, newState); + } + }, + 'click .js-poker-finish'(e) { + if ($(e.target).hasClass('js-poker-finish')) { + e.preventDefault(); + const now = new Date(); + Meteor.call('cards.setPokerEnd', this.data()._id, now); + } + }, + + 'click .js-poker-replay'(e) { + if ($(e.target).hasClass('js-poker-replay')) { + e.preventDefault(); + this.currentCard = this.currentData(); + Meteor.call('cards.replayPoker', this.currentCard._id); + Meteor.call('cards.unsetPokerEnd', this.currentCard._id); + Meteor.call('cards.unsetPokerEstimation', this.currentCard._id); + } + }, + 'click .js-poker-estimation'(event) { + event.preventDefault(); + + const ruleTitle = this.find('#pokerEstimation').value; + if (ruleTitle !== undefined && ruleTitle !== '') { + this.find('#pokerEstimation').value = ''; + + if (ruleTitle) { + Meteor.call('cards.setPokerEstimation', this.data()._id, parseInt(ruleTitle, 10)); + } else { + Meteor.call('cards.unsetPokerEstimation', this.data()._id); + } + } + }, + // Drag and drop file upload handlers + 'dragover .js-card-details'(event) { + // Only prevent default for file drags to avoid interfering with other drag operations + const dataTransfer = event.originalEvent.dataTransfer; + if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { + event.preventDefault(); + event.stopPropagation(); + } + }, + 'dragenter .js-card-details'(event) { + const dataTransfer = event.originalEvent.dataTransfer; + if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { + event.preventDefault(); + event.stopPropagation(); + const card = this.data(); + const board = card.board(); + // Only allow drag-and-drop if user can modify card and board allows attachments + if (Utils.canModifyCard() && board && board.allowsAttachments) { + $(event.currentTarget).addClass('is-dragging-over'); + } + } + }, + 'dragleave .js-card-details'(event) { + const dataTransfer = event.originalEvent.dataTransfer; + if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { + event.preventDefault(); + event.stopPropagation(); + $(event.currentTarget).removeClass('is-dragging-over'); + } + }, + 'drop .js-card-details'(event) { + const dataTransfer = event.originalEvent.dataTransfer; + if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { + event.preventDefault(); + event.stopPropagation(); + $(event.currentTarget).removeClass('is-dragging-over'); + + const card = this.data(); + const board = card.board(); + + // Check permissions + if (!Utils.canModifyCard() || !board || !board.allowsAttachments) { + return; + } + + // Check if this is a file drop (not a checklist item reorder) + if (!dataTransfer.files || dataTransfer.files.length === 0) { + return; + } + + const files = dataTransfer.files; + if (files && files.length > 0) { + handleFileUpload(card, files); + } + } + }, + }, + ]; }, -}); +}).register('cardDetails'); Template.cardDetails.helpers({ isPopup() { @@ -779,16 +600,18 @@ Template.cardDetailsPopup.helpers({ }, }); -Template.exportCardPopup.helpers({ +BlazeComponent.extendComponent({ + template() { + return 'exportCard'; + }, withApi() { return Template.instance().apiEnabled.get(); }, exportUrlCardPDF() { - const card = getCurrentCardFromContext({ ignorePopupCard: true }) || this; const params = { - boardId: card.boardId || Session.get('currentBoard'), - listId: card.listId, - cardId: card._id || card.cardId, + boardId: Session.get('currentBoard'), + listId: this.listId, + cardId: this.cardId, }; const queryParams = { authToken: Accounts._storedLoginToken(), @@ -800,13 +623,11 @@ Template.exportCardPopup.helpers({ ); }, exportFilenameCardPDF() { - const card = getCurrentCardFromContext({ ignorePopupCard: true }) || this; - return `${String(card.title || 'export-card') - .replace(/[^a-z0-9._-]+/gi, '-') - .replace(/-+/g, '-') - .replace(/^-|-$/g, '') || 'export-card'}.pdf`; + //const boardId = Session.get('currentBoard'); + //return `export-card-pdf-${boardId}.xlsx`; + return `export-card.pdf`; }, -}); +}).register('exportCardPopup'); // only allow number input Template.editCardSortOrderForm.onRendered(function () { @@ -820,71 +641,48 @@ Template.editCardSortOrderForm.onRendered(function () { }); }); -// inlinedCardDescription extends the normal inlinedForm to support UnsavedEdits -// draft feature for card descriptions. -Template.inlinedCardDescription.onCreated(function () { - this.isOpen = new ReactiveVar(false); +// We extends the normal InlinedForm component to support UnsavedEdits draft +// feature. +(class extends InlinedForm { + _getUnsavedEditKey() { + return { + fieldName: 'cardDescription', + // XXX Recovering the currentCard identifier form a session variable is + // fragile because this variable may change for instance if the route + // change. We should use some component props instead. + docId: Utils.getCurrentCardId(), + }; + } - this._getUnsavedEditKey = () => ({ - fieldName: 'cardDescription', - docId: getCardId(), - }); - - this._getValue = () => { - const input = this.find('textarea,input[type=text]'); - return this.isOpen.get() && input && input.value.replaceAll(/[ \f\r\t\v]+$/gm, ''); - }; - - this._close = (isReset = false) => { + close(isReset = false) { if (this.isOpen.get() && !isReset) { - const draft = (this._getValue() || '').trim(); - const card = getCurrentCardFromContext(); + const draft = this.getValue().trim(); + let card = Utils.getCurrentCard(); if (card && draft !== card.getDescription()) { - UnsavedEdits.set(this._getUnsavedEditKey(), this._getValue()); + UnsavedEdits.set(this._getUnsavedEditKey(), this.getValue()); } } - this.isOpen.set(false); - }; + super.close(); + } - this._reset = () => { + reset() { UnsavedEdits.reset(this._getUnsavedEditKey()); - this._close(true); - }; -}); + this.close(true); + } -Template.inlinedCardDescription.helpers({ - isOpen() { - return Template.instance().isOpen; - }, -}); - -Template.inlinedCardDescription.events({ - 'click .js-close-inlined-form'(evt, tpl) { - tpl._reset(); - }, - 'click .js-open-inlined-form'(evt, tpl) { - evt.preventDefault(); - EscapeActions.clickExecute(evt.target, 'inlinedForm'); - tpl.isOpen.set(true); - }, - 'keydown form textarea'(evt, tpl) { - if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) { - tpl.find('button[type=submit]').click(); - } - }, - submit(evt, tpl) { - const data = Template.currentData(); - if (data.autoclose !== false) { - Tracker.afterFlush(() => { - tpl._close(); - }); - } - }, -}); + events() { + const parentEvents = InlinedForm.prototype.events()[0]; + return [ + { + ...parentEvents, + 'click .js-close-inlined-form': this.reset, + }, + ]; + } +}.register('inlinedCardDescription')); Template.cardDetailsActionsPopup.helpers({ isWatching() { - if (!this || typeof this.findWatcher !== 'function') return false; return this.findWatcher(Meteor.userId()); }, @@ -915,67 +713,64 @@ Template.cardDetailsActionsPopup.events({ 'click .js-convert-checklist-item-to-card': Popup.open('convertChecklistItemToCard'), 'click .js-copy-checklist-cards': Popup.open('copyManyCards'), 'click .js-set-card-color': Popup.open('setCardColor'), - async 'click .js-move-card-to-top'(event) { + 'click .js-move-card-to-top'(event) { event.preventDefault(); - const card = Cards.findOne(getCardId()); - if (!card) return; - const minOrder = card.getMinSort() || 0; - await card.move(card.boardId, card.swimlaneId, card.listId, minOrder - 1); + const minOrder = this.getMinSort(); + this.move(this.boardId, this.swimlaneId, this.listId, minOrder - 1); Popup.back(); }, - async 'click .js-move-card-to-bottom'(event) { + 'click .js-move-card-to-bottom'(event) { event.preventDefault(); - const card = Cards.findOne(getCardId()); - if (!card) return; - const maxOrder = card.getMaxSort() || 0; - await card.move(card.boardId, card.swimlaneId, card.listId, maxOrder + 1); + const maxOrder = this.getMaxSort(); + this.move(this.boardId, this.swimlaneId, this.listId, maxOrder + 1); Popup.back(); }, - 'click .js-archive': Popup.afterConfirm('cardArchive', async function () { - const card = Cards.findOne(getCardId()); + 'click .js-archive': Popup.afterConfirm('cardArchive', function () { Popup.close(); - if (!card) return; - await card.archive(); - Utils.goBoardId(card.boardId); + this.archive(); + Utils.goBoardId(this.boardId); }), 'click .js-more': Popup.open('cardMore'), 'click .js-toggle-watch-card'() { - const currentCard = Cards.findOne(getCardId()); - if (!currentCard) return; + const currentCard = this; const level = currentCard.findWatcher(Meteor.userId()) ? null : 'watching'; Meteor.call('watch', 'card', currentCard._id, level, (err, ret) => { if (!err && ret) Popup.close(); }); }, 'click .js-toggle-show-list-on-minicard'() { - const currentCard = Cards.findOne(getCardId()); - if (!currentCard) return; + const currentCard = this; const newValue = !currentCard.showListOnMinicard; Cards.update(currentCard._id, { $set: { showListOnMinicard: newValue } }); Popup.close(); }, }); -Template.editCardTitleForm.onRendered(function () { - autosize(this.$('textarea.js-edit-card-title')); -}); - -Template.editCardTitleForm.events({ - 'click a.fa.fa-copy'(event, tpl) { - const $editor = tpl.$('textarea'); - const promise = Utils.copyTextToClipboard($editor[0].value); - - const $tooltip = tpl.$('.copied-tooltip'); - Utils.showCopied(promise, $tooltip); +BlazeComponent.extendComponent({ + onRendered() { + autosize(this.$('textarea.js-edit-card-title')); }, - 'keydown .js-edit-card-title'(event) { - // If enter key was pressed, submit the data - // Unless the shift key is also being pressed - if (event.keyCode === 13 && !event.shiftKey) { - $('.js-submit-edit-card-title-form').click(); - } - }, -}); + events() { + return [ + { + 'click a.fa.fa-copy'(event) { + const $editor = this.$('textarea'); + const promise = Utils.copyTextToClipboard($editor[0].value); + + const $tooltip = this.$('.copied-tooltip'); + Utils.showCopied(promise, $tooltip); + }, + 'keydown .js-edit-card-title'(event) { + // If enter key was pressed, submit the data + // Unless the shift key is also being pressed + if (event.keyCode === 13 && !event.shiftKey) { + $('.js-submit-edit-card-title-form').click(); + } + }, + } + ]; + } +}).register('editCardTitleForm'); Template.cardMembersPopup.onCreated(function () { let currBoard = Utils.getCurrentBoard(); @@ -984,13 +779,6 @@ Template.cardMembersPopup.onCreated(function () { }); Template.cardMembersPopup.events({ - 'click .js-select-member'(event) { - const card = getCurrentCardFromContext(); - if (!card) return; - const memberId = this.userId; - card.toggleMember(memberId); - event.preventDefault(); - }, 'keyup .card-members-filter'(event) { const members = filterMembers(event.target.value); Template.instance().members.set(members); @@ -998,24 +786,8 @@ Template.cardMembersPopup.events({ }); Template.cardMembersPopup.helpers({ - isCardMember() { - const card = getCurrentCardFromContext(); - if (!card) return false; - const cardMembers = card.getMembers(); - - return _.contains(cardMembers, this.userId); - }, - members() { - const members = Template.instance().members.get(); - const uniqueMembers = _.uniq(members, 'userId'); - return _.sortBy(uniqueMembers, member => { - const user = ReactiveCache.getUser(member.userId); - return user ? user.profile.fullname : ''; - }); - }, - userData() { - return ReactiveCache.getUser(this.userId); + return _.sortBy(Template.instance().members.get(),'fullname'); }, }); @@ -1063,159 +835,94 @@ Template.editCardAssignerForm.events({ }, }); -/** - * Helper: register standard board/swimlane/list/card dialog helpers and events - * for a template that uses BoardSwimlaneListCardDialog. - */ -function registerCardDialogTemplate(templateName) { - Template[templateName].helpers({ - boards() { - return Template.instance().dialog.boards(); - }, - swimlanes() { - return Template.instance().dialog.swimlanes(); - }, - lists() { - return Template.instance().dialog.lists(); - }, - cards() { - return Template.instance().dialog.cards(); - }, - isDialogOptionBoardId(boardId) { - return Template.instance().dialog.isDialogOptionBoardId(boardId); - }, - isDialogOptionSwimlaneId(swimlaneId) { - return Template.instance().dialog.isDialogOptionSwimlaneId(swimlaneId); - }, - isDialogOptionListId(listId) { - return Template.instance().dialog.isDialogOptionListId(listId); - }, - isDialogOptionCardId(cardId) { - return Template.instance().dialog.isDialogOptionCardId(cardId); - }, - isTitleDefault(title) { - return Template.instance().dialog.isTitleDefault(title); - }, - }); - - Template[templateName].events({ - async 'click .js-done'(event, tpl) { - const dialog = tpl.dialog; - const boardSelect = tpl.$('.js-select-boards')[0]; - const boardId = boardSelect?.options[boardSelect?.selectedIndex]?.value; - - const listSelect = tpl.$('.js-select-lists')[0]; - const listId = listSelect?.options[listSelect?.selectedIndex]?.value; - - const swimlaneSelect = tpl.$('.js-select-swimlanes')[0]; - const swimlaneId = swimlaneSelect?.options[swimlaneSelect?.selectedIndex]?.value; - - const cardSelect = tpl.$('.js-select-cards')[0]; - const cardId = cardSelect?.options?.length > 0 - ? cardSelect.options[cardSelect.selectedIndex].value - : null; - - const options = { boardId, swimlaneId, listId, cardId }; - try { - await dialog.setDone(cardId, options); - } catch (e) { - console.error('Error in card dialog operation:', e); - } - Popup.back(2); - }, - 'change .js-select-boards'(event, tpl) { - tpl.dialog.getBoardData($(event.currentTarget).val()); - }, - 'change .js-select-swimlanes'(event, tpl) { - tpl.dialog.selectedSwimlaneId.set($(event.currentTarget).val()); - tpl.dialog.setFirstListId(); - }, - 'change .js-select-lists'(event, tpl) { - tpl.dialog.selectedListId.set($(event.currentTarget).val()); - tpl.dialog.selectedCardId.set(''); - }, - 'change .js-select-cards'(event, tpl) { - tpl.dialog.selectedCardId.set($(event.currentTarget).val()); - }, - }); -} - /** Move Card Dialog */ -Template.moveCardPopup.onCreated(function () { - this.dialog = new BoardSwimlaneListCardDialog(this, { - getDialogOptions() { - return ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions(); - }, - async setDone(cardId, options) { - const tpl = Template.instance(); - const position = tpl.$('input[name="position"]:checked').val(); - - ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options); - const card = Template.currentData(); - let sortIndex = 0; - - if (cardId) { - const targetCard = ReactiveCache.getCard(cardId); - if (targetCard) { - const targetSort = targetCard.sort || 0; - if (position === 'above') { - sortIndex = targetSort - 0.5; - } else { - sortIndex = targetSort + 0.5; - } - } - } else { - const maxSort = card.getMaxSort(options.listId, options.swimlaneId); - sortIndex = (typeof maxSort === 'number' && !Number.isNaN(maxSort)) ? maxSort + 1 : 0; - } - - await card.move(options.boardId, options.swimlaneId, options.listId, sortIndex); - }, - }); -}); -registerCardDialogTemplate('moveCardPopup'); +(class extends DialogWithBoardSwimlaneList { + getDialogOptions() { + const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions(); + return ret; + } + setDone(boardId, swimlaneId, listId, options) { + ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options); + const card = this.data(); + const minOrder = card.getMinSort(listId, swimlaneId); + card.move(boardId, swimlaneId, listId, minOrder - 1); + } +}).register('moveCardPopup'); /** Copy Card Dialog */ -Template.copyCardPopup.onCreated(function () { - this.dialog = new BoardSwimlaneListCardDialog(this, { - getDialogOptions() { - return ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions(); - }, - async setDone(cardId, options) { - const tpl = Template.instance(); - const textarea = tpl.$('#copy-card-title'); - const title = textarea.val().trim(); - const position = tpl.$('input[name="position"]:checked').val(); +(class extends DialogWithBoardSwimlaneList { + getDialogOptions() { + const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions(); + return ret; + } + setDone(boardId, swimlaneId, listId, options) { + ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options); + const card = this.data(); - ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options); - const card = Template.currentData(); + // const textarea = $('#copy-card-title'); + const textarea = this.$('#copy-card-title'); + const title = textarea.val().trim(); - if (title) { - const newCardId = await Meteor.callAsync('copyCard', card._id, options.boardId, options.swimlaneId, options.listId, true, {title: title}); + if (title) { + // insert new card to the top of new list + const newCardId = Meteor.call('copyCard', card._id, boardId, swimlaneId, listId, true, {title: title}); - if (newCardId) { - const newCard = ReactiveCache.getCard(newCardId); - if (newCard) { - let sortIndex = 0; + // In case the filter is active we need to add the newly inserted card in + // the list of exceptions -- cards that are not filtered. Otherwise the + // card will disappear instantly. + // See https://github.com/wekan/wekan/issues/80 + Filter.addException(newCardId); + } + } +}).register('copyCardPopup'); - if (cardId) { - const targetCard = ReactiveCache.getCard(cardId); - if (targetCard) { - const targetSort = targetCard.sort || 0; - if (position === 'above') { - sortIndex = targetSort - 0.5; - } else { - sortIndex = targetSort + 0.5; - } - } - } else { - const maxSort = newCard.getMaxSort(options.listId, options.swimlaneId); - sortIndex = (typeof maxSort === 'number' && !Number.isNaN(maxSort)) ? maxSort + 1 : 0; - } +/** Convert Checklist-Item to card dialog */ +(class extends DialogWithBoardSwimlaneList { + getDialogOptions() { + const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions(); + return ret; + } + setDone(boardId, swimlaneId, listId, options) { + ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options); + const card = this.data(); - await newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex); - } - } + const textarea = this.$('#copy-card-title'); + const title = textarea.val().trim(); + + if (title) { + const _id = Cards.insert({ + title: title, + listId: listId, + boardId: boardId, + swimlaneId: swimlaneId, + sort: 0, + }); + const card = ReactiveCache.getCard(_id); + const minOrder = card.getMinSort(); + card.move(card.boardId, card.swimlaneId, card.listId, minOrder - 1); + + Filter.addException(_id); + } + } +}).register('convertChecklistItemToCardPopup'); + +/** Copy many cards dialog */ +(class extends DialogWithBoardSwimlaneList { + getDialogOptions() { + const ret = ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions(); + return ret; + } + setDone(boardId, swimlaneId, listId, options) { + ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options); + const card = this.data(); + + const textarea = this.$('#copy-card-title'); + const title = textarea.val().trim(); + + if (title) { + const titleList = JSON.parse(title); + for (const obj of titleList) { + const newCardId = Meteor.call('copyCard', card._id, boardId, swimlaneId, listId, false, {title: obj.title, description: obj.description}); // In case the filter is active we need to add the newly inserted card in // the list of exceptions -- cards that are not filtered. Otherwise the @@ -1223,221 +930,62 @@ Template.copyCardPopup.onCreated(function () { // See https://github.com/wekan/wekan/issues/80 Filter.addException(newCardId); } - }, - }); -}); -registerCardDialogTemplate('copyCardPopup'); + } + } +}).register('copyManyCardsPopup'); -/** Convert Checklist-Item to card dialog */ -Template.convertChecklistItemToCardPopup.onCreated(function () { - this.dialog = new BoardSwimlaneListCardDialog(this, { - getDialogOptions() { - return ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions(); - }, - async setDone(cardId, options) { - const tpl = Template.instance(); - const textarea = tpl.$('#copy-card-title'); - const title = textarea.val().trim(); - const position = tpl.$('input[name="position"]:checked').val(); +BlazeComponent.extendComponent({ + onCreated() { + this.currentCard = this.currentData(); + this.currentColor = new ReactiveVar(this.currentCard.color); + }, - ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options); - const card = Template.currentData(); - - if (title) { - const _id = Cards.insert({ - title: title, - listId: options.listId, - boardId: options.boardId, - swimlaneId: options.swimlaneId, - sort: 0, - }); - const newCard = ReactiveCache.getCard(_id); - - let sortIndex = 0; - if (cardId) { - const targetCard = ReactiveCache.getCard(cardId); - if (targetCard) { - const targetSort = targetCard.sort || 0; - if (position === 'above') { - sortIndex = targetSort - 0.5; - } else { - sortIndex = targetSort + 0.5; - } - } - } else { - const maxSort = newCard.getMaxSort(options.listId, options.swimlaneId); - sortIndex = (typeof maxSort === 'number' && !Number.isNaN(maxSort)) ? maxSort + 1 : 0; - } - - await newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex); - - Filter.addException(_id); - } - }, - }); -}); -registerCardDialogTemplate('convertChecklistItemToCardPopup'); - -/** Copy many cards dialog */ -Template.copyManyCardsPopup.onCreated(function () { - this.dialog = new BoardSwimlaneListCardDialog(this, { - getDialogOptions() { - return ReactiveCache.getCurrentUser().getMoveAndCopyDialogOptions(); - }, - async setDone(cardId, options) { - const tpl = Template.instance(); - const textarea = tpl.$('#copy-card-title'); - const title = textarea.val().trim(); - const position = tpl.$('input[name="position"]:checked').val(); - - ReactiveCache.getCurrentUser().setMoveAndCopyDialogOption(this.currentBoardId, options); - const card = Template.currentData(); - - if (title) { - const titleList = JSON.parse(title); - for (const obj of titleList) { - const newCardId = await Meteor.callAsync('copyCard', card._id, options.boardId, options.swimlaneId, options.listId, false, {title: obj.title, description: obj.description}); - - if (newCardId) { - const newCard = ReactiveCache.getCard(newCardId); - let sortIndex = 0; - - if (cardId) { - const targetCard = ReactiveCache.getCard(cardId); - if (targetCard) { - const targetSort = targetCard.sort || 0; - if (position === 'above') { - sortIndex = targetSort - 0.5; - } else { - sortIndex = targetSort + 0.5; - } - } - } else { - const maxSort = newCard.getMaxSort(options.listId, options.swimlaneId); - sortIndex = (typeof maxSort === 'number' && !Number.isNaN(maxSort)) ? maxSort + 1 : 0; - } - - await newCard.move(options.boardId, options.swimlaneId, options.listId, sortIndex); - } - - // In case the filter is active we need to add the newly inserted card in - // the list of exceptions -- cards that are not filtered. Otherwise the - // card will disappear instantly. - // See https://github.com/wekan/wekan/issues/80 - Filter.addException(newCardId); - } - } - }, - }); -}); -registerCardDialogTemplate('copyManyCardsPopup'); - -Template.setCardColorPopup.onCreated(function () { - const cardId = getCardId(); - this.currentCard = Cards.findOne(cardId); - this.currentColor = new ReactiveVar(this.currentCard?.color); -}); - -Template.setCardColorPopup.helpers({ colors() { return ALLOWED_COLORS.map((color) => ({ color, name: '' })); }, isSelected(color) { - const tpl = Template.instance(); - if (tpl.currentColor.get() === null) { + if (this.currentColor.get() === null) { return color === 'white'; } - return tpl.currentColor.get() === color; - }, -}); - -Template.setCardColorPopup.events({ - 'click .js-palette-color'(event, tpl) { - tpl.currentColor.set(Template.currentData().color); - }, - async 'click .js-submit'(event, tpl) { - event.preventDefault(); - const card = Cards.findOne(getCardId()); - if (!card) return; - await card.setColor(tpl.currentColor.get()); - Popup.back(); - }, - async 'click .js-remove-color'(event) { - event.preventDefault(); - const card = Cards.findOne(getCardId()); - if (!card) return; - await card.setColor(null); - Popup.back(); - }, -}); - -Template.setSelectionColorPopup.onCreated(function () { - this.currentColor = new ReactiveVar(null); -}); - -Template.setSelectionColorPopup.helpers({ - colors() { - return ALLOWED_COLORS.map((color) => ({ color, name: '' })); + return this.currentColor.get() === color; }, - isSelected(color) { - return Template.instance().currentColor.get() === color; + events() { + return [ + { + 'click .js-palette-color'() { + this.currentColor.set(this.currentData().color); + }, + 'click .js-submit'(event) { + event.preventDefault(); + this.currentCard.setColor(this.currentColor.get()); + Popup.back(); + }, + 'click .js-remove-color'(event) { + event.preventDefault(); + this.currentCard.setColor(null); + Popup.back(); + }, + }, + ]; }, -}); +}).register('setCardColorPopup'); -Template.setSelectionColorPopup.events({ - 'click .js-palette-color'(event, tpl) { - // Extract color from class name like "card-details-red" - const classes = $(event.currentTarget).attr('class').split(' '); - const colorClass = classes.find(cls => cls.startsWith('card-details-')); - const color = colorClass ? colorClass.replace('card-details-', '') : null; - tpl.currentColor.set(color); - }, - async 'click .js-submit'(event, tpl) { - event.preventDefault(); - const color = tpl.currentColor.get(); - // Use MultiSelection to get selected cards and set color on each - for (const card of ReactiveCache.getCards(MultiSelection.getMongoSelector())) { - await card.setColor(color); - } - Popup.back(); - }, - async 'click .js-remove-color'(event, tpl) { - event.preventDefault(); - // Use MultiSelection to get selected cards and remove color from each - for (const card of ReactiveCache.getCards(MultiSelection.getMongoSelector())) { - await card.setColor(null); - } - Popup.back(); - }, -}); - -Template.cardMorePopup.onCreated(function () { - const cardId = getCardId(); - this.currentCard = Cards.findOne(cardId); - this.parentBoard = new ReactiveVar(null); - this.parentCard = this.currentCard?.parentCard(); - if (this.parentCard) { - const list = $('.js-field-parent-card'); - list.val(this.parentCard._id); - this.parentBoard.set(this.parentCard.board()._id); - } else { - this.parentBoard.set(null); - } - - this.setParentCardId = (cardId) => { - if (cardId) { - this.parentCard = ReactiveCache.getCard(cardId); +BlazeComponent.extendComponent({ + onCreated() { + this.currentCard = this.currentData(); + this.parentBoard = new ReactiveVar(null); + this.parentCard = this.currentCard.parentCard(); + if (this.parentCard) { + const list = $('.js-field-parent-card'); + list.val(this.parentCard._id); + this.parentBoard.set(this.parentCard.board()._id); } else { - this.parentCard = null; + this.parentBoard.set(null); } - const card = Cards.findOne(getCardId()); - if (card) card.setParentId(cardId); - }; -}); + }, -Template.cardMorePopup.helpers({ boards() { const ret = ReactiveCache.getBoards( { @@ -1453,11 +1001,10 @@ Template.cardMorePopup.helpers({ }, cards() { - const tpl = Template.instance(); - const currentId = getCardId(); - if (tpl.parentBoard.get()) { + const currentId = Utils.getCurrentCardId(); + if (this.parentBoard.get()) { const ret = ReactiveCache.getCards({ - boardId: tpl.parentBoard.get(), + boardId: this.parentBoard.get(), _id: { $ne: currentId }, }); return ret; @@ -1467,276 +1014,580 @@ Template.cardMorePopup.helpers({ }, isParentBoard() { - const tpl = Template.instance(); - const board = Template.currentData(); - if (tpl.parentBoard.get()) { - return board._id === tpl.parentBoard.get(); + const board = this.currentData(); + if (this.parentBoard.get()) { + return board._id === this.parentBoard.get(); } return false; }, isParentCard() { - const tpl = Template.instance(); - const card = Template.currentData(); - if (tpl.parentCard) { - return card._id === tpl.parentCard; + const card = this.currentData(); + if (this.parentCard) { + return card._id === this.parentCard; } return false; }, -}); -Template.cardMorePopup.events({ - 'click .js-copy-card-link-to-clipboard'(event, tpl) { - const promise = Utils.copyTextToClipboard(location.origin + document.getElementById('cardURL').value); - - const $tooltip = tpl.$('.copied-tooltip'); - Utils.showCopied(promise, $tooltip); - }, - 'click .js-delete': Popup.afterConfirm('cardDelete', function () { - const card = Cards.findOne(getCardId()); - Popup.close(); - if (!card) return; - // verify that there are no linked cards - if (ReactiveCache.getCards({ linkedId: card._id }).length === 0) { - Cards.remove(card._id); + setParentCardId(cardId) { + if (cardId) { + this.parentCard = ReactiveCache.getCard(cardId); } else { - // TODO: Maybe later we can list where the linked cards are. - // Now here is popup with a hint that the card cannot be deleted - // as there are linked cards. - // Related: - // client/components/lists/listHeader.js about line 248 - // https://github.com/wekan/wekan/issues/2785 - const message = `${TAPi18n.__( - 'delete-linked-card-before-this-card', - )} linkedId: ${card._id - } at client/components/cards/cardDetails.js and https://github.com/wekan/wekan/issues/2785`; - alert(message); + this.parentCard = null; } - Utils.goBoardId(card.boardId); - }), - 'change .js-field-parent-board'(event, tpl) { - const selection = $(event.currentTarget).val(); - const list = $('.js-field-parent-card'); - if (selection === 'none') { - tpl.parentBoard.set(null); - } else { - subManager.subscribe('board', $(event.currentTarget).val(), false); - tpl.parentBoard.set(selection); - list.prop('disabled', false); - } - tpl.setParentCardId(null); + this.currentCard.setParentId(cardId); }, - 'change .js-field-parent-card'(event, tpl) { - const selection = $(event.currentTarget).val(); - tpl.setParentCardId(selection); - }, -}); -Template.cardStartVotingPopup.onCreated(function () { - const cardId = getCardId(); - this.currentCard = Cards.findOne(cardId); - this.voteQuestion = new ReactiveVar(this.currentCard?.voteQuestion); -}); + events() { + return [ + { + 'click .js-copy-card-link-to-clipboard'(event) { + const promise = Utils.copyTextToClipboard(location.origin + document.getElementById('cardURL').value); -Template.cardStartVotingPopup.helpers({ - getVoteQuestion() { - const card = Cards.findOne(getCardId()); - return card && card.getVoteQuestion ? card.getVoteQuestion() : null; + const $tooltip = this.$('.copied-tooltip'); + Utils.showCopied(promise, $tooltip); + }, + 'click .js-delete': Popup.afterConfirm('cardDelete', function () { + Popup.close(); + // verify that there are no linked cards + if (ReactiveCache.getCards({ linkedId: this._id }).length === 0) { + Cards.remove(this._id); + } else { + // TODO: Maybe later we can list where the linked cards are. + // Now here is popup with a hint that the card cannot be deleted + // as there are linked cards. + // Related: + // client/components/lists/listHeader.js about line 248 + // https://github.com/wekan/wekan/issues/2785 + const message = `${TAPi18n.__( + 'delete-linked-card-before-this-card', + )} linkedId: ${this._id + } at client/components/cards/cardDetails.js and https://github.com/wekan/wekan/issues/2785`; + alert(message); + } + Utils.goBoardId(this.boardId); + }), + 'change .js-field-parent-board'(event) { + const selection = $(event.currentTarget).val(); + const list = $('.js-field-parent-card'); + if (selection === 'none') { + this.parentBoard.set(null); + } else { + subManager.subscribe('board', $(event.currentTarget).val(), false); + this.parentBoard.set(selection); + list.prop('disabled', false); + } + this.setParentCardId(null); + }, + 'change .js-field-parent-card'(event) { + const selection = $(event.currentTarget).val(); + this.setParentCardId(selection); + }, + }, + ]; }, - votePublic() { - const card = Cards.findOne(getCardId()); - return card && card.votePublic ? card.votePublic() : false; - }, - voteAllowNonBoardMembers() { - const card = Cards.findOne(getCardId()); - return card && card.voteAllowNonBoardMembers ? card.voteAllowNonBoardMembers() : false; - }, - getVoteEnd() { - const card = Cards.findOne(getCardId()); - return card && card.getVoteEnd ? card.getVoteEnd() : null; - }, -}); +}).register('cardMorePopup'); -Template.cardStartVotingPopup.events({ - 'click .js-end-date': Popup.open('editVoteEndDate'), - 'submit .edit-vote-question'(evt) { - evt.preventDefault(); - const card = Cards.findOne(getCardId()); - if (!card) return; - const voteQuestion = evt.target.vote.value; - const publicVote = $('#vote-public').hasClass('is-checked'); - const allowNonBoardMembers = $('#vote-allow-non-members').hasClass( - 'is-checked', - ); - const endString = card.getVoteEnd(); - Meteor.call('cards.setVoteQuestion', card._id, voteQuestion, publicVote, allowNonBoardMembers); - if (endString) { - Meteor.call('cards.setVoteEnd', card._id, new Date(endString)); - } - Popup.back(); +BlazeComponent.extendComponent({ + onCreated() { + this.currentCard = this.currentData(); + this.voteQuestion = new ReactiveVar(this.currentCard.voteQuestion); }, - 'click .js-remove-vote': Popup.afterConfirm('deleteVote', function () { - const card = Cards.findOne(getCardId()); - if (!card) return; - Meteor.call('cards.unsetVote', card._id); - Popup.back(); - }), - 'click a.js-toggle-vote-public'(event) { - event.preventDefault(); - $('#vote-public').toggleClass('is-checked'); - }, - 'click a.js-toggle-vote-allow-non-members'(event) { - event.preventDefault(); - $('#vote-allow-non-members').toggleClass('is-checked'); - }, -}); -Template.positiveVoteMembersPopup.helpers({ - voteMemberPositive() { - const card = Cards.findOne(getCardId()); - return card ? card.voteMemberPositive() : []; + events() { + return [ + { + 'click .js-end-date': Popup.open('editVoteEndDate'), + 'submit .edit-vote-question'(evt) { + evt.preventDefault(); + const voteQuestion = evt.target.vote.value; + const publicVote = $('#vote-public').hasClass('is-checked'); + const allowNonBoardMembers = $('#vote-allow-non-members').hasClass( + 'is-checked', + ); + const endString = this.currentCard.getVoteEnd(); + Meteor.call('cards.setVoteQuestion', this.currentCard._id, voteQuestion, publicVote, allowNonBoardMembers); + if (endString) { + Meteor.call('cards.setVoteEnd', this.currentCard._id, endString); + } + Popup.back(); + }, + 'click .js-remove-vote': Popup.afterConfirm('deleteVote', () => { + event.preventDefault(); + Meteor.call('cards.unsetVote', this.currentCard._id); + Popup.back(); + }), + 'click a.js-toggle-vote-public'(event) { + event.preventDefault(); + $('#vote-public').toggleClass('is-checked'); + }, + 'click a.js-toggle-vote-allow-non-members'(event) { + event.preventDefault(); + $('#vote-allow-non-members').toggleClass('is-checked'); + }, + }, + ]; }, -}); - -Template.negativeVoteMembersPopup.helpers({ - voteMemberNegative() { - const card = Cards.findOne(getCardId()); - return card ? card.voteMemberNegative() : []; - }, -}); - -Template.cardDeletePopup.helpers({ - archived() { - const card = Cards.findOne(getCardId()); - return card ? card.archived : false; - }, -}); - -Template.cardArchivePopup.helpers({ - archived() { - const card = Cards.findOne(getCardId()); - return card ? card.archived : false; - }, -}); +}).register('cardStartVotingPopup'); // editVoteEndDatePopup -Template.editVoteEndDatePopup.onCreated(function () { - const card = Cards.findOne(getCardId()); - setupDatePicker(this, { - defaultTime: formatDateTime(now()), - initialDate: card?.getVoteEnd ? (card.getVoteEnd() || undefined) : undefined, - }); -}); +(class extends DatePicker { + onCreated() { + super.onCreated(formatDateTime(now())); + this.data().getVoteEnd() && this.date.set(new Date(this.data().getVoteEnd())); + } + events() { + return [ + { + 'submit .edit-date'(evt) { + evt.preventDefault(); -Template.editVoteEndDatePopup.onRendered(function () { - datePickerRendered(this); -}); + // if no time was given, init with 12:00 + const time = + evt.target.time.value || + formatTime(new Date().setHours(12, 0, 0)); -Template.editVoteEndDatePopup.helpers(datePickerHelpers()); + const dateString = `${evt.target.date.value} ${time}`; -Template.editVoteEndDatePopup.events(datePickerEvents({ - storeDate(date) { - Meteor.call('cards.setVoteEnd', this.datePicker.card._id, date); - }, - deleteDate() { - Meteor.call('cards.unsetVoteEnd', this.datePicker.card._id); - }, -})); + /* + const newDate = parseDate(dateString, ['L LT'], true); + if (newDate.isValid()) { + // if active vote - store it + if (this.currentData().getVoteQuestion()) { + this._storeDate(newDate.toDate()); + Popup.back(); + } else { + this.currentData().vote = { end: newDate.toDate() }; // set vote end temp + Popup.back(); + } -Template.cardStartPlanningPokerPopup.onCreated(function () { - const cardId = getCardId(); - this.currentCard = Cards.findOne(cardId); - this.pokerQuestion = new ReactiveVar(this.currentCard?.pokerQuestion); -}); -Template.cardStartPlanningPokerPopup.helpers({ - getPokerQuestion() { - const card = Cards.findOne(getCardId()); - return card && card.getPokerQuestion ? card.getPokerQuestion() : null; - }, - pokerAllowNonBoardMembers() { - const card = Cards.findOne(getCardId()); - return card && card.pokerAllowNonBoardMembers ? card.pokerAllowNonBoardMembers() : false; - }, - getPokerEnd() { - const card = Cards.findOne(getCardId()); - return card && card.getPokerEnd ? card.getPokerEnd() : null; - }, -}); + */ -Template.cardStartPlanningPokerPopup.events({ - 'click .js-end-date': Popup.open('editPokerEndDate'), - 'submit .edit-poker-question'(evt) { - evt.preventDefault(); - const card = Cards.findOne(getCardId()); - if (!card) return; - const pokerQuestion = true; - const allowNonBoardMembers = $('#poker-allow-non-members').hasClass( - 'is-checked', - ); - const endString = card.getPokerEnd(); + // Try to parse different date formats using native Date parsing + const formats = [ + 'YYYY-MM-DD HH:mm', + 'MM/DD/YYYY HH:mm', + 'DD.MM.YYYY HH:mm', + 'DD/MM/YYYY HH:mm', + 'DD-MM-YYYY HH:mm' + ]; + + let parsedDate = null; + for (const format of formats) { + parsedDate = parseDate(dateString, [format], true); + if (parsedDate) break; + } + + // Fallback to native Date parsing + if (!parsedDate) { + parsedDate = new Date(dateString); + } - Meteor.call('cards.setPokerQuestion', card._id, pokerQuestion, allowNonBoardMembers); - if (endString) { - Meteor.call('cards.setPokerEnd', card._id, new Date(endString)); - } - Popup.back(); + if (isValidDate(parsedDate)) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(usaDate.toDate()); + } else { + this.currentData().poker = { end: usaDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (euroAmDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(euroAmDate.toDate()); + } else { + this.currentData().poker = { end: euroAmDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (euro24hDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(euro24hDate.toDate()); + this.card.setPokerEnd(euro24hDate.toDate()); + } else { + this.currentData().poker = { end: euro24hDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (eurodotDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(eurodotDate.toDate()); + this.card.setPokerEnd(eurodotDate.toDate()); + } else { + this.currentData().poker = { end: eurodotDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (minusDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(minusDate.toDate()); + this.card.setPokerEnd(minusDate.toDate()); + } else { + this.currentData().poker = { end: minusDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (slashDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(slashDate.toDate()); + this.card.setPokerEnd(slashDate.toDate()); + } else { + this.currentData().poker = { end: slashDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (dotDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(dotDate.toDate()); + this.card.setPokerEnd(dotDate.toDate()); + } else { + this.currentData().poker = { end: dotDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (brezhonegDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(brezhonegDate.toDate()); + this.card.setPokerEnd(brezhonegDate.toDate()); + } else { + this.currentData().poker = { end: brezhonegDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (hrvatskiDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(hrvatskiDate.toDate()); + this.card.setPokerEnd(hrvatskiDate.toDate()); + } else { + this.currentData().poker = { end: hrvatskiDate.toDate() }; // set poker end temp + Popup.back(); + } + } else if (latviaDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(latviaDate.toDate()); + this.card.setPokerEnd(latviaDate.toDate()); + } else { + this.currentData().poker = { end: latviaDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (nederlandsDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(nederlandsDate.toDate()); + this.card.setPokerEnd(nederlandsDate.toDate()); + } else { + this.currentData().poker = { end: nederlandsDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (greekDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(greekDate.toDate()); + this.card.setPokerEnd(greekDate.toDate()); + } else { + this.currentData().poker = { end: greekDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (macedonianDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(macedonianDate.toDate()); + this.card.setPokerEnd(macedonianDate.toDate()); + } else { + this.currentData().poker = { end: macedonianDate.toDate() }; // set poker end temp + } + Popup.back(); + } else { + this.error.set('invalid-date'); + evt.target.date.focus(); + } + }, + 'click .js-delete-date'(evt) { + evt.preventDefault(); + this._deleteDate(); + Popup.back(); + }, + }, + ]; + } + _storeDate(newDate) { + Meteor.call('cards.setVoteEnd', this.card._id, newDate); + } + _deleteDate() { + Meteor.call('cards.unsetVoteEnd', this.card._id); + } +}.register('editVoteEndDatePopup')); + +BlazeComponent.extendComponent({ + onCreated() { + this.currentCard = this.currentData(); + this.pokerQuestion = new ReactiveVar(this.currentCard.pokerQuestion); }, - 'click .js-remove-poker': Popup.afterConfirm('deletePoker', function () { - const card = Cards.findOne(getCardId()); - if (!card) return; - Meteor.call('cards.unsetPoker', card._id); - Popup.back(); - }), - 'click a.js-toggle-poker-allow-non-members'(event) { - event.preventDefault(); - $('#poker-allow-non-members').toggleClass('is-checked'); + + events() { + return [ + { + 'click .js-end-date': Popup.open('editPokerEndDate'), + 'submit .edit-poker-question'(evt) { + evt.preventDefault(); + const pokerQuestion = true; + const allowNonBoardMembers = $('#poker-allow-non-members').hasClass( + 'is-checked', + ); + const endString = this.currentCard.getPokerEnd(); + + Meteor.call('cards.setPokerQuestion', this.currentCard._id, pokerQuestion, allowNonBoardMembers); + if (endString) { + Meteor.call('cards.setPokerEnd', this.currentCard._id, new Date(endString)); + } + Popup.back(); + }, + 'click .js-remove-poker': Popup.afterConfirm('deletePoker', (event) => { + Meteor.call('cards.unsetPoker', this.currentCard._id); + Popup.back(); + }), + 'click a.js-toggle-poker-allow-non-members'(event) { + event.preventDefault(); + $('#poker-allow-non-members').toggleClass('is-checked'); + }, + }, + ]; }, -}); +}).register('cardStartPlanningPokerPopup'); // editPokerEndDatePopup -Template.editPokerEndDatePopup.onCreated(function () { - const card = Cards.findOne(getCardId()); - setupDatePicker(this, { - defaultTime: formatDateTime(now()), - initialDate: card?.getPokerEnd ? (card.getPokerEnd() || undefined) : undefined, - }); -}); +(class extends DatePicker { + onCreated() { + super.onCreated(formatDateTime(now())); + this.data().getPokerEnd() && + this.date.set(new Date(this.data().getPokerEnd())); + } -Template.editPokerEndDatePopup.onRendered(function () { - datePickerRendered(this); -}); + /* + Tried to use dateFormat and timeFormat from client/components/lib/datepicker.js + to make detecting all date formats not necessary, + but got error "language mk does not exist". + Maybe client/components/lib/datepicker.jade could have hidden input field for + datepicker format that could be used to detect date format? -Template.editPokerEndDatePopup.helpers(datePickerHelpers()); + dateFormat() { + return moment.localeData().longDateFormat('L'); + } -Template.editPokerEndDatePopup.events(datePickerEvents({ - storeDate(date) { - Meteor.call('cards.setPokerEnd', this.datePicker.card._id, date); - }, - deleteDate() { - Meteor.call('cards.unsetPokerEnd', this.datePicker.card._id); - }, -})); + timeFormat() { + return moment.localeData().longDateFormat('LT'); + } + + const newDate = parseDate(dateString, [dateformat() + ' ' + timeformat()], true); + */ + + events() { + return [ + { + 'submit .edit-date'(evt) { + evt.preventDefault(); + + // if no time was given, init with 12:00 + const time = + evt.target.time.value || + formatTime(new Date().setHours(12, 0, 0)); + + const dateString = `${evt.target.date.value} ${time}`; + + /* + Tried to use dateFormat and timeFormat from client/components/lib/datepicker.js + to make detecting all date formats not necessary, + but got error "language mk does not exist". + Maybe client/components/lib/datepicker.jade could have hidden input field for + datepicker format that could be used to detect date format? + + const newDate = parseDate(dateString, [dateformat() + ' ' + timeformat()], true); + + if (newDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(newDate.toDate()); + Popup.back(); + } else { + this.currentData().poker = { end: newDate.toDate() }; // set poker end temp + Popup.back(); + } + */ + + // Try to parse different date formats using native Date parsing + const formats = [ + 'YYYY-MM-DD HH:mm', + 'MM/DD/YYYY HH:mm', + 'DD.MM.YYYY HH:mm', + 'DD/MM/YYYY HH:mm', + 'DD-MM-YYYY HH:mm' + ]; + + let parsedDate = null; + for (const format of formats) { + parsedDate = parseDate(dateString, [format], true); + if (parsedDate) break; + } + + // Fallback to native Date parsing + if (!parsedDate) { + parsedDate = new Date(dateString); + } + + if (isValidDate(parsedDate)) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(usaDate.toDate()); + } else { + this.currentData().poker = { end: usaDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (euroAmDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(euroAmDate.toDate()); + } else { + this.currentData().poker = { end: euroAmDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (euro24hDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(euro24hDate.toDate()); + this.card.setPokerEnd(euro24hDate.toDate()); + } else { + this.currentData().poker = { end: euro24hDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (eurodotDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(eurodotDate.toDate()); + this.card.setPokerEnd(eurodotDate.toDate()); + } else { + this.currentData().poker = { end: eurodotDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (minusDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(minusDate.toDate()); + this.card.setPokerEnd(minusDate.toDate()); + } else { + this.currentData().poker = { end: minusDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (slashDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(slashDate.toDate()); + this.card.setPokerEnd(slashDate.toDate()); + } else { + this.currentData().poker = { end: slashDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (dotDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(dotDate.toDate()); + this.card.setPokerEnd(dotDate.toDate()); + } else { + this.currentData().poker = { end: dotDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (brezhonegDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(brezhonegDate.toDate()); + this.card.setPokerEnd(brezhonegDate.toDate()); + } else { + this.currentData().poker = { end: brezhonegDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (hrvatskiDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(hrvatskiDate.toDate()); + this.card.setPokerEnd(hrvatskiDate.toDate()); + } else { + this.currentData().poker = { end: hrvatskiDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (latviaDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(latviaDate.toDate()); + this.card.setPokerEnd(latviaDate.toDate()); + } else { + this.currentData().poker = { end: latviaDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (nederlandsDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(nederlandsDate.toDate()); + this.card.setPokerEnd(nederlandsDate.toDate()); + } else { + this.currentData().poker = { end: nederlandsDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (greekDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(greekDate.toDate()); + this.card.setPokerEnd(greekDate.toDate()); + } else { + this.currentData().poker = { end: greekDate.toDate() }; // set poker end temp + } + Popup.back(); + } else if (macedonianDate.isValid()) { + // if active poker - store it + if (this.currentData().getPokerQuestion()) { + this._storeDate(macedonianDate.toDate()); + this.card.setPokerEnd(macedonianDate.toDate()); + } else { + this.currentData().poker = { end: macedonianDate.toDate() }; // set poker end temp + } + Popup.back(); + } else { + // this.error.set('invalid-date); + this.error.set('invalid-date' + ' ' + dateString); + evt.target.date.focus(); + } + }, + 'click .js-delete-date'(evt) { + evt.preventDefault(); + this._deleteDate(); + Popup.back(); + }, + }, + ]; + } + _storeDate(newDate) { + Meteor.call('cards.setPokerEnd', this.card._id, newDate); + } + _deleteDate() { + Meteor.call('cards.unsetPokerEnd', this.card._id); + } +}.register('editPokerEndDatePopup')); // Close the card details pane by pressing escape EscapeActions.register( 'detailsPane', - async () => { + () => { // if card description diverges from database due to editing // ask user whether changes should be applied if (ReactiveCache.getCurrentUser()) { if (ReactiveCache.getCurrentUser().profile.rescueCardDescription == true) { - const currentCard = getCurrentCardFromContext(); - const cardDetailsElement = getCardDetailsElement(currentCard?._id); - const currentDescription = cardDetailsElement?.querySelector( - '.editor.js-new-description-input', - ); - if (currentDescription?.value && currentCard && !(currentDescription.value === currentCard.getDescription())) { + currentDescription = document.getElementsByClassName("editor js-new-description-input").item(0) + if (currentDescription?.value && !(currentDescription.value === Utils.getCurrentCard().getDescription())) { if (confirm(TAPi18n.__('rescue-card-description-dialogue'))) { - await currentCard.setDescription(currentDescription.value); + Utils.getCurrentCard().setDescription(document.getElementsByClassName("editor js-new-description-input").item(0).value); // Save it! - console.log(currentDescription.value); - console.log("current description", currentCard.getDescription()); + console.log(document.getElementsByClassName("editor js-new-description-input").item(0).value); + console.log("current description", Utils.getCurrentCard().getDescription()); } else { // Do nothing! console.log('Description changes were not saved to the database.'); @@ -1770,8 +1621,7 @@ Template.cardAssigneesPopup.onCreated(function () { Template.cardAssigneesPopup.events({ 'click .js-select-assignee'(event) { - const card = getCurrentCardFromContext(); - if (!card) return; + const card = Utils.getCurrentCard(); const assigneeId = this.userId; card.toggleAssignee(assigneeId); event.preventDefault(); @@ -1784,23 +1634,17 @@ Template.cardAssigneesPopup.events({ Template.cardAssigneesPopup.helpers({ isCardAssignee() { - const card = getCurrentCardFromContext(); - if (!card) return false; + const card = Template.parentData(); const cardAssignees = card.getAssignees(); return _.contains(cardAssignees, this.userId); }, members() { - const members = Template.instance().members.get(); - const uniqueMembers = _.uniq(members, 'userId'); - return _.sortBy(uniqueMembers, member => { - const user = ReactiveCache.getUser(member.userId); - return user ? user.profile.fullname : ''; - }); + return _.sortBy(Template.instance().members.get(),'fullname'); }, - userData() { + user() { return ReactiveCache.getUser(this.userId); }, }); @@ -1821,8 +1665,7 @@ Template.cardAssigneePopup.helpers({ }, isCardAssignee() { - const card = getCurrentCardFromContext(); - if (!card) return false; + const card = Template.parentData(); const cardAssignees = card.getAssignees(); return _.contains(cardAssignees, this.userId); diff --git a/client/components/cards/cardTime.jade b/client/components/cards/cardTime.jade index d8a2a5984..88f52fef6 100644 --- a/client/components/cards/cardTime.jade +++ b/client/components/cards/cardTime.jade @@ -1,4 +1,4 @@ -template(name="editCardSpentTimePopup") +template(name="editCardSpentTime") .edit-card-time form.edit-time .fields @@ -13,7 +13,7 @@ template(name="editCardSpentTimePopup") button.primary.wide.left.js-submit-time(type="submit") {{_ 'save'}} button.js-delete-time.negate.wide.right {{_ 'delete'}} -template(name="cardSpentTime") +template(name="timeBadge") if canModifyCard a.js-edit-time.card-time(title="{{_ 'time'}}" class="{{#if getIsOvertime}}card-label-red{{else}}card-label-green{{/if}}") | ⏱️ {{showTime}} diff --git a/client/components/cards/cardTime.js b/client/components/cards/cardTime.js index 7cebe5431..fe6b7b03c 100644 --- a/client/components/cards/cardTime.js +++ b/client/components/cards/cardTime.js @@ -1,91 +1,85 @@ import { TAPi18n } from '/imports/i18n'; -import Cards from '/models/cards'; -import { getCurrentCardIdFromContext } from '/client/lib/currentCard'; -function getCardId() { - return getCurrentCardIdFromContext(); -} - -Template.editCardSpentTimePopup.onCreated(function () { - this.error = new ReactiveVar(''); - this.card = Cards.findOne(getCardId()); -}); - -Template.editCardSpentTimePopup.helpers({ - error() { - return Template.instance().error; +BlazeComponent.extendComponent({ + template() { + return 'editCardSpentTime'; }, - card() { - return Cards.findOne(getCardId()); + onCreated() { + this.error = new ReactiveVar(''); + this.card = this.data(); }, - getIsOvertime() { - const card = Cards.findOne(getCardId()); - return card?.getIsOvertime ? card.getIsOvertime() : false; - }, -}); - -Template.editCardSpentTimePopup.events({ - //TODO : need checking this portion - 'submit .edit-time'(evt, tpl) { - evt.preventDefault(); - const card = Cards.findOne(getCardId()); - if (!card) return; - - const spentTime = parseFloat(evt.target.time.value); - let isOvertime = false; - if ($('#overtime').attr('class').indexOf('is-checked') >= 0) { - isOvertime = true; - } - if (spentTime >= 0) { - card.setSpentTime(spentTime); - card.setIsOvertime(isOvertime); - Popup.back(); - } else { - tpl.error.set('invalid-time'); - evt.target.time.focus(); - } - }, - 'click .js-delete-time'(evt) { - evt.preventDefault(); - const card = Cards.findOne(getCardId()); - if (!card) return; - card.setSpentTime(null); - card.setIsOvertime(false); - Popup.back(); - }, - 'click a.js-toggle-overtime'(evt) { - const card = Cards.findOne(getCardId()); - if (!card) return; - card.setIsOvertime(!card.getIsOvertime()); + toggleOvertime() { + this.card.setIsOvertime(!this.card.getIsOvertime()); $('#overtime .materialCheckBox').toggleClass('is-checked'); $('#overtime').toggleClass('is-checked'); }, -}); + storeTime(spentTime, isOvertime) { + this.card.setSpentTime(spentTime); + this.card.setIsOvertime(isOvertime); + }, + deleteTime() { + this.card.setSpentTime(null); + this.card.setIsOvertime(false); + }, + events() { + return [ + { + //TODO : need checking this portion + 'submit .edit-time'(evt) { + evt.preventDefault(); -Template.cardSpentTime.helpers({ + const spentTime = parseFloat(evt.target.time.value); + //const isOvertime = this.card.getIsOvertime(); + let isOvertime = false; + if ($('#overtime').attr('class').indexOf('is-checked') >= 0) { + isOvertime = true; + } + if (spentTime >= 0) { + this.storeTime(spentTime, isOvertime); + Popup.back(); + } else { + this.error.set('invalid-time'); + evt.target.time.focus(); + } + }, + 'click .js-delete-time'(evt) { + evt.preventDefault(); + this.deleteTime(); + Popup.back(); + }, + 'click a.js-toggle-overtime': this.toggleOvertime, + }, + ]; + }, +}).register('editCardSpentTimePopup'); + +BlazeComponent.extendComponent({ + template() { + return 'timeBadge'; + }, + onCreated() { + const self = this; + self.time = ReactiveVar(); + }, showTitle() { - const card = Cards.findOne(this._id) || this; - if (card.getIsOvertime && card.getIsOvertime()) { + if (this.data().getIsOvertime()) { return `${TAPi18n.__( 'overtime', - )} ${card.getSpentTime()} ${TAPi18n.__('hours')}`; - } else if (card.getSpentTime) { + )} ${this.data().getSpentTime()} ${TAPi18n.__('hours')}`; + } else { return `${TAPi18n.__( 'card-spent', - )} ${card.getSpentTime()} ${TAPi18n.__('hours')}`; + )} ${this.data().getSpentTime()} ${TAPi18n.__('hours')}`; } - return ''; }, showTime() { - const card = Cards.findOne(this._id) || this; - return card.getSpentTime ? card.getSpentTime() : ''; + return this.data().getSpentTime(); }, - getIsOvertime() { - const card = Cards.findOne(this._id) || this; - return card.getIsOvertime ? card.getIsOvertime() : false; + events() { + return [ + { + 'click .js-edit-time': Popup.open('editCardSpentTime'), + }, + ]; }, -}); - -Template.cardSpentTime.events({ - 'click .js-edit-time': Popup.open('editCardSpentTime'), -}); +}).register('cardSpentTime'); diff --git a/client/components/cards/checklists.css b/client/components/cards/checklists.css index 073e7ec79..566df27f0 100644 --- a/client/components/cards/checklists.css +++ b/client/components/cards/checklists.css @@ -37,12 +37,10 @@ textarea.js-edit-checklist-item { .checklist-progress-bar-container .checklist-progress-bar { width: 80%; height: 10px; - background-color: #e0e0e0; - border-radius: 16px; } .checklist-progress-bar-container .checklist-progress-bar .checklist-progress { - color: #fff; - background-color: #666; + color: #fff !important; + background-color: #2196f3 !important; padding: 0.01em 16px; border-radius: 16px; height: 100%; @@ -74,6 +72,10 @@ textarea.js-edit-checklist-item { padding-top: 3px; float: left; } +.checklist-title span.fa.checklist-handle.fa-arrows::before { + content: "↕️" !important; + font-family: inherit !important; +} #card-details-overlay { top: 0; bottom: -600px; @@ -150,6 +152,10 @@ textarea.js-edit-checklist-item { padding-top: 2px; padding-right: 10px; } +.checklist-item span.fa.checklistitem-handle.fa-arrows::before { + content: "↕️" !important; + font-family: inherit !important; +} .js-delete-checklist-item, .js-convert-checklist-item-to-card { margin: 0 0 0.5em 1.33em; diff --git a/client/components/cards/checklists.jade b/client/components/cards/checklists.jade index c2b60837d..39ed211b1 100644 --- a/client/components/cards/checklists.jade +++ b/client/components/cards/checklists.jade @@ -1,15 +1,14 @@ template(name="checklists") .checklists-title h3.card-details-item-title - i.fa.fa-check + | ✅ | {{_ 'checklists'}} if canModifyCard - +inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId - position="top") + +inlinedForm(autoclose=false classNames="js-add-checklist" cardId = cardId position="top") +addChecklistItemForm else a.add-checklist-top.js-open-inlined-form(title="{{_ 'add-checklist'}}") - i.fa.fa-plus + | ➕ if currentUser.isBoardMember .material-toggle-switch(title="{{_ 'hide-finished-checklist'}}") //span.toggle-switch-title @@ -29,7 +28,7 @@ template(name="checklists") +addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=false) else a.add-checklist.js-open-inlined-form(title="{{_ 'add-checklist'}}") - i.fa.fa-plus + | ➕ template(name="checklistDetail") .js-checklist.checklist.nodragscroll @@ -39,7 +38,7 @@ template(name="checklistDetail") .checklist-title span if canModifyCard - a.fa.fa-navicon.checklist-details-menu.js-open-checklist-details-menu(title="{{_ 'checklistActionsPopup-title'}}") + a.checklist-details-menu.js-open-checklist-details-menu(title="{{_ 'checklistActionsPopup-title'}}") if canModifyCard h4.title.js-open-inlined-form.is-editable @@ -64,12 +63,13 @@ template(name="checklistDeletePopup") button.js-confirm.negate.full(type="submit") {{_ 'delete'}} template(name="addChecklistItemForm") - a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}") + a(title="{{_ 'copy-text-to-clipboard'}}") span.copied-tooltip {{_ 'copied'}} textarea.js-add-checklist-item(rows='1' autofocus) .edit-controls.clearfix button.primary.confirm.js-submit-add-checklist-item-form(type="submit") {{_ 'save'}} - a.fa.fa-times-thin.js-close-inlined-form(title="{{_ 'close-add-checklist-item'}}") + a.js-close-inlined-form(title="{{_ 'close-add-checklist-item'}}") + | ❌ if showNewlineBecomesNewChecklistItem .material-toggle-switch(title="{{_ 'newlineBecomesNewChecklistItem'}}") input.toggle-switch(type="checkbox" id="toggleNewlineBecomesNewChecklistItem") @@ -82,7 +82,7 @@ template(name="addChecklistItemForm") | {{_ 'originOrder'}} template(name="editChecklistItemForm") - a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}") + a(title="{{_ 'copy-text-to-clipboard'}}") span.copied-tooltip {{_ 'copied'}} textarea.js-edit-checklist-item(rows='1' autofocus dir="auto") if $eq type 'item' @@ -91,12 +91,13 @@ template(name="editChecklistItemForm") = checklist.title .edit-controls.clearfix button.primary.confirm.js-submit-edit-checklist-item-form(type="submit") {{_ 'save'}} - a.fa.fa-times-thin.js-close-inlined-form(title="{{_ 'close-edit-checklist-item'}}") - span(title=createdAt) {{ displayDate createdAt }} + a.js-close-inlined-form(title="{{_ 'close-edit-checklist-item'}}") + | ❌ + span(title=createdAt) {{ moment createdAt }} if canModifyCard a.js-delete-checklist-item {{_ "delete"}}... a.js-convert-checklist-item-to-card - i.fa.fa-copy + | 📋 | {{_ 'convertChecklistItemToCardPopup-title'}} template(name="checklistItems") @@ -106,7 +107,7 @@ template(name="checklistItems") +addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true position="top") else a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}") - i.fa.fa-plus + | ➕ .checklist-items.js-checklist-items each item in checklist.items +inlinedForm(classNames="js-edit-checklist-item" item = item checklist = checklist) @@ -118,7 +119,7 @@ template(name="checklistItems") +addChecklistItemForm(checklist=checklist showNewlineBecomesNewChecklistItem=true) else a.add-checklist-item.js-open-inlined-form(title="{{_ 'add-checklist-item'}}") - i.fa.fa-plus + | ➕ template(name='checklistItemDetail') .js-checklist-item.checklist-item(class="{{#if item.isFinished }}is-checked{{#if checklist.hideCheckedChecklistItems}} invisible{{/if}}{{/if}}{{#if checklist.hideAllChecklistItems}} is-checked invisible{{/if}}" @@ -126,8 +127,7 @@ template(name='checklistItemDetail') if canModifyCard .check-box-container .check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}") - if isTouchScreenOrShowDesktopDragHandles - span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}") + span.fa.checklistitem-handle(class="fa-arrows" title="{{_ 'dragChecklistItem'}}") .item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}") +viewer = item.title @@ -141,16 +141,16 @@ template(name="checklistActionsPopup") ul.pop-over-list li a.js-delete-checklist.delete-checklist - i.fa.fa-trash + | 🗑️ | {{_ "delete"}} ... a.js-move-checklist.move-checklist - i.fa.fa-arrow-right + | ➡️ | {{_ "moveChecklist"}} ... a.js-copy-checklist.copy-checklist - i.fa.fa-copy + | 📋 | {{_ "copyChecklist"}} ... a.js-hide-checked-checklist-items - i.fa.fa-eye-slash + | 🙈 | {{_ "hideCheckedChecklistItems"}} ... .material-toggle-switch(title="{{_ 'hide-checked-items'}}") if checklist.hideCheckedChecklistItems @@ -159,7 +159,7 @@ template(name="checklistActionsPopup") input.toggle-switch(type="checkbox" id="toggleHideCheckedChecklistItems_{{checklist._id}}") label.toggle-label(for="toggleHideCheckedChecklistItems_{{checklist._id}}") a.js-hide-all-checklist-items - i.fa.fa-ban + | 🚫 | {{_ "hideAllChecklistItems"}} ... .material-toggle-switch(title="{{_ 'hideAllChecklistItems'}}") if checklist.hideAllChecklistItems diff --git a/client/components/cards/checklists.js b/client/components/cards/checklists.js index 7dd0c3343..6762eab02 100644 --- a/client/components/cards/checklists.js +++ b/client/components/cards/checklists.js @@ -2,7 +2,7 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; import Cards from '/models/cards'; import Boards from '/models/boards'; -import { BoardSwimlaneListCardDialog } from '/client/lib/dialogWithBoardSwimlaneListCard'; +import { DialogWithBoardSwimlaneListCard } from '/client/lib/dialogWithBoardSwimlaneListCard'; const subManager = new SubsManager(); const { calculateIndexData, capitalize } = Utils; @@ -45,63 +45,55 @@ function initSorting(items) { }); } -Template.checklistDetail.onRendered(function () { - const tpl = this; - tpl.itemsDom = this.$('.js-checklist-items'); - initSorting(tpl.itemsDom); - tpl.itemsDom.mousedown(function (evt) { - evt.stopPropagation(); - }); +BlazeComponent.extendComponent({ + onRendered() { + const self = this; + self.itemsDom = this.$('.js-checklist-items'); + initSorting(self.itemsDom); + self.itemsDom.mousedown(function (evt) { + evt.stopPropagation(); + }); - function userIsMember() { - return ReactiveCache.getCurrentUser()?.isBoardMember(); - } - - // Disable sorting if the current user is not a board member - tpl.autorun(() => { - const $itemsDom = $(tpl.itemsDom); - if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) { - $(tpl.itemsDom).sortable('option', 'disabled', !userIsMember()); - if (Utils.isTouchScreenOrShowDesktopDragHandles()) { - $(tpl.itemsDom).sortable({ - handle: 'span.fa.checklistitem-handle', - }); - } + function userIsMember() { + return ReactiveCache.getCurrentUser()?.isBoardMember(); } - }); -}); -Template.checklistDetail.helpers({ + // Disable sorting if the current user is not a board member + self.autorun(() => { + const $itemsDom = $(self.itemsDom); + if ($itemsDom.data('uiSortable') || $itemsDom.data('sortable')) { + $(self.itemsDom).sortable('option', 'disabled', !userIsMember()); + if (Utils.isTouchScreenOrShowDesktopDragHandles()) { + $(self.itemsDom).sortable({ + handle: 'span.fa.checklistitem-handle', + }); + } + } + }); + }, + /** returns the finished percent of the checklist */ finishedPercent() { - const ret = this.checklist.finishedPercent(); + const ret = this.data().checklist.finishedPercent(); return ret; }, -}); +}).register('checklistDetail'); -Template.checklists.helpers({ - checklists() { - const card = ReactiveCache.getCard(this.cardId); - const ret = card.checklists(); - return ret; - }, -}); - -Template.checklists.events({ - 'click .js-open-checklist-details-menu': Popup.open('checklistActions'), - 'submit .js-add-checklist'(event, tpl) { +BlazeComponent.extendComponent({ + addChecklist(event) { event.preventDefault(); - const textarea = tpl.find('textarea.js-add-checklist-item'); + const textarea = this.find('textarea.js-add-checklist-item'); const title = textarea.value.trim(); - let cardId = Template.currentData().cardId; + let cardId = this.currentData().cardId; const card = ReactiveCache.getCard(cardId); + //if (card.isLinked()) cardId = card.linkedId; if (card.isLinkedCard()) { cardId = card.linkedId; } let sortIndex; let checklistItemIndex; - if (Template.currentData().position === 'top') { + if (this.currentData().position === 'top') { sortIndex = Utils.calculateIndexData(null, card.firstChecklist()).base; checklistItemIndex = 0; } else { @@ -115,34 +107,27 @@ Template.checklists.events({ title, sort: sortIndex, }); - tpl.$('.js-close-inlined-form').click(); + this.closeAllInlinedForms(); setTimeout(() => { - tpl.$('.add-checklist-item') + this.$('.add-checklist-item') .eq(checklistItemIndex) .click(); }, 100); } }, - 'submit .js-edit-checklist-title'(event, tpl) { + addChecklistItem(event) { event.preventDefault(); - const textarea = tpl.find('textarea.js-edit-checklist-item'); + const textarea = this.find('textarea.js-add-checklist-item'); + const newlineBecomesNewChecklistItem = this.find('input#toggleNewlineBecomesNewChecklistItem'); + const newlineBecomesNewChecklistItemOriginOrder = this.find('input#toggleNewlineBecomesNewChecklistItemOriginOrder'); const title = textarea.value.trim(); - const checklist = Template.currentData().checklist; - checklist.setTitle(title); - }, - 'submit .js-add-checklist-item'(event, tpl) { - event.preventDefault(); - const textarea = tpl.find('textarea.js-add-checklist-item'); - const newlineBecomesNewChecklistItem = tpl.find('input#toggleNewlineBecomesNewChecklistItem'); - const newlineBecomesNewChecklistItemOriginOrder = tpl.find('input#toggleNewlineBecomesNewChecklistItemOriginOrder'); - const title = textarea.value.trim(); - const checklist = Template.currentData().checklist; + const checklist = this.currentData().checklist; if (title) { let checklistItems = [title]; if (newlineBecomesNewChecklistItem.checked) { checklistItems = title.split('\n').map(_value => _value.trim()); - if (Template.currentData().position === 'top') { + if (this.currentData().position === 'top') { if (newlineBecomesNewChecklistItemOriginOrder.checked === false) { checklistItems = checklistItems.reverse(); } @@ -150,7 +135,7 @@ Template.checklists.events({ } let addIndex; let sortIndex; - if (Template.currentData().position === 'top') { + if (this.currentData().position === 'top') { sortIndex = Utils.calculateIndexData(null, checklist.firstItem()).base; addIndex = -1; } else { @@ -171,39 +156,33 @@ Template.checklists.events({ textarea.value = ''; textarea.focus(); }, - 'submit .js-edit-checklist-item'(event, tpl) { - event.preventDefault(); - const textarea = tpl.find('textarea.js-edit-checklist-item'); - const title = textarea.value.trim(); - const item = Template.currentData().item; - item.setTitle(title); - }, - 'click .js-convert-checklist-item-to-card': Popup.open('convertChecklistItemToCard'), - async 'click .js-delete-checklist-item'() { - const checklist = Template.currentData().checklist; - const item = Template.currentData().item; + + deleteItem() { + const checklist = this.currentData().checklist; + const item = this.currentData().item; if (checklist && item && item._id) { ChecklistItems.remove(item._id); } }, - 'focus .js-add-checklist-item'(event) { - // If a new checklist is created, pre-fill the title and select it. - const checklist = Template.currentData().checklist; - if (!checklist) { - const textarea = event.target; - textarea.value = capitalize(TAPi18n.__('r-checklist')); - textarea.select(); - } - }, - // add and delete checklist / checklist-item - 'click .js-open-inlined-form'(event, tpl) { - tpl.$('.js-close-inlined-form').click(); - }, - 'click #toggleHideFinishedChecklist'(event) { + + editChecklist(event) { event.preventDefault(); - Template.currentData().card.toggleHideFinishedChecklist(); + const textarea = this.find('textarea.js-edit-checklist-item'); + const title = textarea.value.trim(); + const checklist = this.currentData().checklist; + checklist.setTitle(title); }, - keydown(event) { + + editChecklistItem(event) { + event.preventDefault(); + + const textarea = this.find('textarea.js-edit-checklist-item'); + const title = textarea.value.trim(); + const item = this.currentData().item; + item.setTitle(title); + }, + + pressKey(event) { //If user press enter key inside a form, submit it //Unless the user is also holding down the 'shift' key if (event.keyCode === 13 && !event.shiftKey) { @@ -212,201 +191,201 @@ Template.checklists.events({ $form.find('button[type=submit]').click(); } }, -}); -// NOTE: boardsSwimlanesAndLists template was removed from jade but JS was left behind. -// This is dead code — the template no longer exists in any jade file. - -Template.addChecklistItemForm.onRendered(function () { - autosize(this.$('textarea.js-add-checklist-item')); -}); - -Template.addChecklistItemForm.events({ - 'click a.fa.fa-copy'(event, tpl) { - const $editor = tpl.$('textarea'); - const promise = Utils.copyTextToClipboard($editor[0].value); - - const $tooltip = tpl.$('.copied-tooltip'); - Utils.showCopied(promise, $tooltip); - }, -}); - -Template.checklistActionsPopup.events({ - 'click .js-delete-checklist': Popup.afterConfirm('checklistDelete', function () { - Popup.back(2); - const checklist = this.checklist; - if (checklist && checklist._id) { - Checklists.remove(checklist._id); + focusChecklistItem(event) { + // If a new checklist is created, pre-fill the title and select it. + const checklist = this.currentData().checklist; + if (!checklist) { + const textarea = event.target; + textarea.value = capitalize(TAPi18n.__('r-checklist')); + textarea.select(); } - }), - 'click .js-move-checklist': Popup.open('moveChecklist'), - 'click .js-copy-checklist': Popup.open('copyChecklist'), - 'click .js-hide-checked-checklist-items'(event) { - event.preventDefault(); - Template.currentData().checklist.toggleHideCheckedChecklistItems(); - Popup.back(); }, - 'click .js-hide-all-checklist-items'(event) { - event.preventDefault(); - Template.currentData().checklist.toggleHideAllChecklistItems(); - Popup.back(); + + /** closes all inlined forms (checklist and checklist-item input fields) */ + closeAllInlinedForms() { + this.$('.js-close-inlined-form').click(); + }, + + events() { + return [ + { + 'click .js-open-checklist-details-menu': Popup.open('checklistActions'), + 'submit .js-add-checklist': this.addChecklist, + 'submit .js-edit-checklist-title': this.editChecklist, + 'submit .js-add-checklist-item': this.addChecklistItem, + 'submit .js-edit-checklist-item': this.editChecklistItem, + 'click .js-convert-checklist-item-to-card': Popup.open('convertChecklistItemToCard'), + 'click .js-delete-checklist-item': this.deleteItem, + 'focus .js-add-checklist-item': this.focusChecklistItem, + // add and delete checklist / checklist-item + 'click .js-open-inlined-form': this.closeAllInlinedForms, + 'click #toggleHideFinishedChecklist'(event) { + event.preventDefault(); + this.data().card.toggleHideFinishedChecklist(); + }, + keydown: this.pressKey, + }, + ]; + }, +}).register('checklists'); + +BlazeComponent.extendComponent({ + onCreated() { + subManager.subscribe('board', Session.get('currentBoard'), false); + this.selectedBoardId = new ReactiveVar(Session.get('currentBoard')); + }, + + boards() { + const ret = ReactiveCache.getBoards( + { + archived: false, + 'members.userId': Meteor.userId(), + _id: { $ne: ReactiveCache.getCurrentUser().getTemplatesBoardId() }, + }, + { + sort: { sort: 1 /* boards default sorting */ }, + }, + ); + return ret; + }, + + swimlanes() { + const board = ReactiveCache.getBoard(this.selectedBoardId.get()); + return board.swimlanes(); + }, + + aBoardLists() { + const board = ReactiveCache.getBoard(this.selectedBoardId.get()); + return board.lists(); + }, + + events() { + return [ + { + 'change .js-select-boards'(event) { + this.selectedBoardId.set($(event.currentTarget).val()); + subManager.subscribe('board', this.selectedBoardId.get(), false); + }, + }, + ]; + }, +}).register('boardsSwimlanesAndLists'); + +Template.checklists.helpers({ + checklists() { + const card = ReactiveCache.getCard(this.cardId); + const ret = card.checklists(); + return ret; }, }); -Template.editChecklistItemForm.onRendered(function () { - autosize(this.$('textarea.js-edit-checklist-item')); -}); - -Template.editChecklistItemForm.events({ - 'click a.fa.fa-copy'(event, tpl) { - const $editor = tpl.$('textarea'); - const promise = Utils.copyTextToClipboard($editor[0].value); - - const $tooltip = tpl.$('.copied-tooltip'); - Utils.showCopied(promise, $tooltip); +BlazeComponent.extendComponent({ + onRendered() { + autosize(this.$('textarea.js-add-checklist-item')); }, -}); + events() { + return [ + { + 'click a.fa.fa-copy'(event) { + const $editor = this.$('textarea'); + const promise = Utils.copyTextToClipboard($editor[0].value); + + const $tooltip = this.$('.copied-tooltip'); + Utils.showCopied(promise, $tooltip); + }, + } + ]; + } +}).register('addChecklistItemForm'); + +BlazeComponent.extendComponent({ + events() { + return [ + { + 'click .js-delete-checklist': Popup.afterConfirm('checklistDelete', function () { + Popup.back(2); + const checklist = this.checklist; + if (checklist && checklist._id) { + Checklists.remove(checklist._id); + } + }), + 'click .js-move-checklist': Popup.open('moveChecklist'), + 'click .js-copy-checklist': Popup.open('copyChecklist'), + 'click .js-hide-checked-checklist-items'(event) { + event.preventDefault(); + this.data().checklist.toggleHideCheckedChecklistItems(); + Popup.back(); + }, + 'click .js-hide-all-checklist-items'(event) { + event.preventDefault(); + this.data().checklist.toggleHideAllChecklistItems(); + Popup.back(); + }, + } + ] + } +}).register('checklistActionsPopup'); + +BlazeComponent.extendComponent({ + onRendered() { + autosize(this.$('textarea.js-edit-checklist-item')); + }, + events() { + return [ + { + 'click a.fa.fa-copy'(event) { + const $editor = this.$('textarea'); + const promise = Utils.copyTextToClipboard($editor[0].value); + + const $tooltip = this.$('.copied-tooltip'); + Utils.showCopied(promise, $tooltip); + }, + } + ]; + } +}).register('editChecklistItemForm'); Template.checklistItemDetail.helpers({ }); -Template.checklistItemDetail.events({ - 'click .js-checklist-item .check-box-container'() { - const checklist = Template.currentData().checklist; - const item = Template.currentData().item; +BlazeComponent.extendComponent({ + toggleItem() { + const checklist = this.currentData().checklist; + const item = this.currentData().item; if (checklist && item && item._id) { item.toggleItem(); } }, -}); - -/** - * Helper to find the dialog instance from a parent popup template. - * copyAndMoveChecklist is included inside moveChecklistPopup / copyChecklistPopup, - * so we traverse up the view hierarchy to find the parent template's dialog. - */ -function getParentDialog(tpl) { - let view = tpl.view.parentView; - while (view) { - if (view.templateInstance && view.templateInstance() && view.templateInstance().dialog) { - return view.templateInstance().dialog; - } - view = view.parentView; - } - return null; -} - -/** Shared helpers for copyAndMoveChecklist sub-template */ -Template.copyAndMoveChecklist.helpers({ - boards() { - const dialog = getParentDialog(Template.instance()); - return dialog ? dialog.boards() : []; + events() { + return [ + { + 'click .js-checklist-item .check-box-container': this.toggleItem, + }, + ]; }, - swimlanes() { - const dialog = getParentDialog(Template.instance()); - return dialog ? dialog.swimlanes() : []; - }, - lists() { - const dialog = getParentDialog(Template.instance()); - return dialog ? dialog.lists() : []; - }, - cards() { - const dialog = getParentDialog(Template.instance()); - return dialog ? dialog.cards() : []; - }, - isDialogOptionBoardId(boardId) { - const dialog = getParentDialog(Template.instance()); - return dialog ? dialog.isDialogOptionBoardId(boardId) : false; - }, - isDialogOptionSwimlaneId(swimlaneId) { - const dialog = getParentDialog(Template.instance()); - return dialog ? dialog.isDialogOptionSwimlaneId(swimlaneId) : false; - }, - isDialogOptionListId(listId) { - const dialog = getParentDialog(Template.instance()); - return dialog ? dialog.isDialogOptionListId(listId) : false; - }, - isDialogOptionCardId(cardId) { - const dialog = getParentDialog(Template.instance()); - return dialog ? dialog.isDialogOptionCardId(cardId) : false; - }, - isTitleDefault(title) { - const dialog = getParentDialog(Template.instance()); - return dialog ? dialog.isTitleDefault(title) : title; - }, -}); - -/** - * Helper: register standard card dialog events on a checklist popup template. - * Events bubble up from the copyAndMoveChecklist sub-template to the parent popup. - */ -function registerChecklistDialogEvents(templateName) { - Template[templateName].events({ - async 'click .js-done'(event, tpl) { - const dialog = tpl.dialog; - const boardSelect = tpl.$('.js-select-boards')[0]; - const boardId = boardSelect.options[boardSelect.selectedIndex].value; - - const listSelect = tpl.$('.js-select-lists')[0]; - const listId = listSelect.options[listSelect.selectedIndex].value; - - const swimlaneSelect = tpl.$('.js-select-swimlanes')[0]; - const swimlaneId = swimlaneSelect.options[swimlaneSelect.selectedIndex].value; - - const cardSelect = tpl.$('.js-select-cards')[0]; - const cardId = cardSelect.options.length > 0 - ? cardSelect.options[cardSelect.selectedIndex].value - : null; - - const options = { boardId, swimlaneId, listId, cardId }; - try { - await dialog.setDone(cardId, options); - } catch (e) { - console.error('Error in card dialog operation:', e); - } - Popup.back(2); - }, - 'change .js-select-boards'(event, tpl) { - tpl.dialog.getBoardData($(event.currentTarget).val()); - }, - 'change .js-select-swimlanes'(event, tpl) { - tpl.dialog.selectedSwimlaneId.set($(event.currentTarget).val()); - tpl.dialog.setFirstListId(); - }, - 'change .js-select-lists'(event, tpl) { - tpl.dialog.selectedListId.set($(event.currentTarget).val()); - tpl.dialog.selectedCardId.set(''); - }, - 'change .js-select-cards'(event, tpl) { - tpl.dialog.selectedCardId.set($(event.currentTarget).val()); - }, - }); -} +}).register('checklistItemDetail'); /** Move Checklist Dialog */ -Template.moveChecklistPopup.onCreated(function () { - this.dialog = new BoardSwimlaneListCardDialog(this, { - getDialogOptions() { - return ReactiveCache.getCurrentUser().getMoveChecklistDialogOptions(); - }, - async setDone(cardId, options) { - ReactiveCache.getCurrentUser().setMoveChecklistDialogOption(this.currentBoardId, options); - await Template.currentData().checklist.move(cardId); - }, - }); -}); -registerChecklistDialogEvents('moveChecklistPopup'); +(class extends DialogWithBoardSwimlaneListCard { + getDialogOptions() { + const ret = ReactiveCache.getCurrentUser().getMoveChecklistDialogOptions(); + return ret; + } + setDone(cardId, options) { + ReactiveCache.getCurrentUser().setMoveChecklistDialogOption(this.currentBoardId, options); + this.data().checklist.move(cardId); + } +}).register('moveChecklistPopup'); /** Copy Checklist Dialog */ -Template.copyChecklistPopup.onCreated(function () { - this.dialog = new BoardSwimlaneListCardDialog(this, { - getDialogOptions() { - return ReactiveCache.getCurrentUser().getCopyChecklistDialogOptions(); - }, - async setDone(cardId, options) { - ReactiveCache.getCurrentUser().setCopyChecklistDialogOption(this.currentBoardId, options); - await Template.currentData().checklist.copy(cardId); - }, - }); -}); -registerChecklistDialogEvents('copyChecklistPopup'); +(class extends DialogWithBoardSwimlaneListCard { + getDialogOptions() { + const ret = ReactiveCache.getCurrentUser().getCopyChecklistDialogOptions(); + return ret; + } + setDone(cardId, options) { + ReactiveCache.getCurrentUser().setCopyChecklistDialogOption(this.currentBoardId, options); + this.data().checklist.copy(cardId); + } +}).register('copyChecklistPopup'); diff --git a/client/components/cards/inlinedCardDescription.jade b/client/components/cards/inlinedCardDescription.jade deleted file mode 100644 index c18ed69de..000000000 --- a/client/components/cards/inlinedCardDescription.jade +++ /dev/null @@ -1,6 +0,0 @@ -template(name='inlinedCardDescription') - if isOpen.get - form.inlined-form.js-inlined-form(id=id class=classNames) - +Template.contentBlock - else - +Template.elseBlock diff --git a/client/components/cards/labels.css b/client/components/cards/labels.css index 19a8746a8..f4738a879 100644 --- a/client/components/cards/labels.css +++ b/client/components/cards/labels.css @@ -223,13 +223,9 @@ .card-label-edit-button:hover { background: #dbdbdb; } -ul.edit-labels-pop-over span.label-handle { +ul.edit-labels-pop-over span.fa.label-handle { padding-right: 10px; - display: inline-block; - width: 1.2em; - text-align: center; - color: #999; } -ul.edit-labels-pop-over span.label-handle + .card-label { +ul.edit-labels-pop-over span.fa.label-handle + .card-label { max-width: 180px; } diff --git a/client/components/cards/labels.jade b/client/components/cards/labels.jade index 8d12dc488..d49c939d2 100644 --- a/client/components/cards/labels.jade +++ b/client/components/cards/labels.jade @@ -6,7 +6,7 @@ template(name="formLabel") .palette-colors: each labels span.card-label.palette-color.js-palette-color(class="card-label-{{color}}") if(isSelected color) - i.fa.fa-check + | ✅ template(name="createLabelPopup") form.create-label @@ -28,7 +28,8 @@ template(name="cardLabelsPopup") ul.edit-labels-pop-over each board.labels li.js-card-label-item - a.card-label-edit-button.fa.fa-pencil.js-edit-label + a.card-label-edit-button.js-edit-label + | ✏️ if isTouchScreenOrShowDesktopDragHandles span.fa.label-handle(class="fa-arrows" title="{{_ 'dragLabel'}}") span.card-label.card-label-selectable.js-select-label.card-label-wrapper(class="card-label-{{color}}" @@ -36,5 +37,5 @@ template(name="cardLabelsPopup") +viewer = name if(isLabelSelected ../_id) - i.card-label-selectable-icon.fa.fa-check + | ✅ a.quiet-button.full.js-add-label {{_ 'label-create'}} diff --git a/client/components/cards/labels.js b/client/components/cards/labels.js index 2bf1cd8ce..2962cae77 100644 --- a/client/components/cards/labels.js +++ b/client/components/cards/labels.js @@ -5,32 +5,29 @@ Meteor.startup(() => { labelColors = Boards.simpleSchema()._schema['labels.$.color'].allowedValues; }); -Template.formLabel.onCreated(function () { - this.currentColor = new ReactiveVar(this.data.color); -}); +BlazeComponent.extendComponent({ + onCreated() { + this.currentColor = new ReactiveVar(this.data().color); + }, -Template.formLabel.helpers({ labels() { return labelColors.map(color => ({ color, name: '' })); }, + isSelected(color) { - return Template.instance().currentColor.get() === color; + return this.currentColor.get() === color; }, -}); -Template.formLabel.events({ - 'click .js-palette-color'(event, tpl) { - tpl.currentColor.set(Template.currentData().color); - - const $this = $(event.currentTarget); - - // hide selected ll colors - $('.js-palette-select').addClass('hide'); - - // show select color - $this.find('.js-palette-select').removeClass('hide'); + events() { + return [ + { + 'click .js-palette-color'() { + this.currentColor.set(this.currentData().color); + }, + }, + ]; }, -}); +}).register('formLabel'); Template.createLabelPopup.helpers({ // This is the default color for a new label. We search the first color that @@ -44,66 +41,79 @@ Template.createLabelPopup.helpers({ }, }); -Template.cardLabelsPopup.onRendered(function () { - const tpl = this; - const itemsSelector = 'li.js-card-label-item:not(.placeholder)'; - const $labels = tpl.$('.edit-labels-pop-over'); +BlazeComponent.extendComponent({ + onRendered() { + const itemsSelector = 'li.js-card-label-item:not(.placeholder)'; + const $labels = this.$('.edit-labels-pop-over'); - $labels.sortable({ - connectWith: '.edit-labels-pop-over', - tolerance: 'pointer', - appendTo: '.edit-labels-pop-over', - helper(element, currentItem) { - let ret = currentItem.clone(); - if (currentItem.closest('.popup-container-depth-0').length == 0) - { // only set css transform at every sub-popup, not at the main popup - const content = currentItem.closest('.content')[0] - const offsetLeft = content.offsetLeft; - const offsetTop = $('.pop-over > .header').height() * -1; - ret.css("transform", `translate(${offsetLeft}px, ${offsetTop}px)`); + $labels.sortable({ + connectWith: '.edit-labels-pop-over', + tolerance: 'pointer', + appendTo: '.edit-labels-pop-over', + helper(element, currentItem) { + let ret = currentItem.clone(); + if (currentItem.closest('.popup-container-depth-0').length == 0) + { // only set css transform at every sub-popup, not at the main popup + const content = currentItem.closest('.content')[0] + const offsetLeft = content.offsetLeft; + const offsetTop = $('.pop-over > .header').height() * -1; + ret.css("transform", `translate(${offsetLeft}px, ${offsetTop}px)`); + } + return ret; + }, + distance: 7, + items: itemsSelector, + placeholder: 'card-label-wrapper placeholder', + start(evt, ui) { + ui.helper.css('z-index', 1000); + ui.placeholder.height(ui.helper.height()); + EscapeActions.clickExecute(evt.target, 'inlinedForm'); + }, + stop(evt, ui) { + const newLabelOrderOnlyIds = ui.item.parent().children().toArray().map(_element => Blaze.getData(_element)._id) + const card = Blaze.getData(this); + card.board().setNewLabelOrder(newLabelOrderOnlyIds); + }, + }); + + // Disable drag-dropping if the current user is not a board member or is comment only + this.autorun(() => { + if (Utils.isTouchScreenOrShowDesktopDragHandles()) { + $labels.sortable({ + handle: '.label-handle', + }); } - return ret; - }, - distance: 7, - items: itemsSelector, - placeholder: 'card-label-wrapper placeholder', - start(evt, ui) { - ui.helper.css('z-index', 1000); - ui.placeholder.height(ui.helper.height()); - EscapeActions.clickExecute(evt.target, 'inlinedForm'); - }, - stop(evt, ui) { - const newLabelOrderOnlyIds = ui.item.parent().children().toArray().map(_element => Blaze.getData(_element)._id) - const card = Blaze.getData(this); - card.board().setNewLabelOrder(newLabelOrderOnlyIds); - }, - }); - - // Disable drag-dropping if the current user is not a board member or is comment only - tpl.autorun(() => { - if (Utils.isTouchScreenOrShowDesktopDragHandles()) { - $labels.sortable({ - handle: '.label-handle', - }); - } - }); -}); - -Template.cardLabelsPopup.helpers({ - isLabelSelected(cardId) { - return _.contains(ReactiveCache.getCard(cardId).labelIds, this._id); + }); }, -}); + events() { + return [ + { + 'click .js-select-label'(event) { + const card = this.data(); + const labelId = this.currentData()._id; + card.toggleLabel(labelId); + event.preventDefault(); + }, + 'click .js-edit-label': Popup.open('editLabel'), + 'click .js-add-label': Popup.open('createLabel'), + } + ]; + } +}).register('cardLabelsPopup'); Template.cardLabelsPopup.events({ - 'click .js-select-label'(event) { - const card = Template.currentData(); - const labelId = this._id; - card.toggleLabel(labelId); - event.preventDefault(); +}); + +Template.formLabel.events({ + 'click .js-palette-color'(event) { + const $this = $(event.currentTarget); + + // hide selected ll colors + $('.js-palette-select').addClass('hide'); + + // show select color + $this.find('.js-palette-select').removeClass('hide'); }, - 'click .js-edit-label': Popup.open('editLabel'), - 'click .js-add-label': Popup.open('createLabel'), }); Template.createLabelPopup.events({ @@ -115,8 +125,19 @@ Template.createLabelPopup.events({ .$('#labelName') .val() .trim(); - const color = Blaze.getData(templateInstance.find('.fa-check')).color; - board.addLabel(name, color); + + // Find the selected color by looking for the palette color that contains the checkmark + let selectedColor = null; + templateInstance.$('.js-palette-color').each(function() { + if ($(this).text().includes('✅')) { + selectedColor = Blaze.getData(this).color; + return false; // break out of loop + } + }); + + if (selectedColor) { + board.addLabel(name, selectedColor); + } Popup.back(); }, }); @@ -134,8 +155,25 @@ Template.editLabelPopup.events({ .$('#labelName') .val() .trim(); - const color = Blaze.getData(templateInstance.find('.fa-check')).color; - board.editLabel(this._id, name, color); + + // Find the selected color by looking for the palette color that contains the checkmark + let selectedColor = null; + templateInstance.$('.js-palette-color').each(function() { + if ($(this).text().includes('✅')) { + selectedColor = Blaze.getData(this).color; + return false; // break out of loop + } + }); + + if (selectedColor) { + board.editLabel(this._id, name, selectedColor); + } Popup.back(); }, }); + +Template.cardLabelsPopup.helpers({ + isLabelSelected(cardId) { + return _.contains(ReactiveCache.getCard(cardId).labelIds, this._id); + }, +}); diff --git a/client/components/cards/minicard.css b/client/components/cards/minicard.css index 32dea839d..8e6158826 100644 --- a/client/components/cards/minicard.css +++ b/client/components/cards/minicard.css @@ -45,10 +45,9 @@ } .minicard-details-menu-with-handle { float: right; - padding-left: 0.7vw; font-size: clamp(14px, 3vw, 18px); - padding: 0; - z-index: 1; + padding-right: 4vw; + padding-left: 0.7vw; } .minicard-details-menu { float: right; @@ -98,7 +97,6 @@ } .minicard .minicard-labels { float: none; - margin-right: 6vw; } .minicard .minicard-labels .minicard-label { width: clamp(12px, 1.5vw, 16px); @@ -113,7 +111,6 @@ } .minicard .minicard-custom-fields { display: block; - margin-right: 6vw; } .minicard .minicard-custom-field { display: flex; @@ -136,25 +133,18 @@ width: clamp(20px, 2.5vw, 28px); height: clamp(20px, 2.5vw, 28px); position: absolute; - right: 0vw; - top: 4vh; + right: 0.7vw; + top: 0.7vh; display: none; - z-index: 1; } @media only screen { .minicard .handle { display: block; } } -.minicard .handle .drag-handle { +.minicard .handle .fa-arrows { font-size: clamp(16px, 3vw, 20px); color: #ccc; - display: inline-block; - width: 1.4em; - text-align: center; -} -.minicard .minicard-title { - margin-right: 1.5vw; } .minicard .minicard-title .card-number { color: #b3b3b3; @@ -174,10 +164,6 @@ display: flex; flex-direction: row; flex-wrap: wrap; - position: relative; - z-index: 5; - margin-right: 6vw; - clear: both; } .minicard .date { margin-right: 0.4vw; @@ -311,6 +297,19 @@ background-color: #1976d2 !important; } +/* Font Awesome icons in minicard dates */ +.minicard .card-date i.fa { + margin-right: 0.3vw; + font-size: 0.9em; + vertical-align: middle; +} + +/* Font Awesome icons in minicard spent time */ +.minicard .card-time i.fa { + margin-right: 0.3vw; + font-size: 0.9em; + vertical-align: middle; +} .minicard .badges { float: left; margin-top: 1vh; @@ -742,80 +741,7 @@ gap: 0.3vw; } -/* Checklist display on minicard */ -.minicard-checklist { - width: 100%; - margin-top: 0.5vh; - margin-bottom: 0.5vh; - padding: 0.3vh 0.5vw; - background-color: rgba(255, 255, 255, 0.8); - border-radius: 0.3vw; - border: 1px solid #e0e0e0; -} - -.minicard-checklist .checklist-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 0.3vh; -} - -.minicard-checklist .checklist-title { +.minicard-list-name i.fa { font-size: 0.8em; - font-weight: bold; - color: #4d4d4d; - flex: 1; -} - -.minicard-checklist .checklist-menu { - font-size: 1.2em; - color: #666; - cursor: pointer; - padding: 0 0.3vw; - border-radius: 0.2vw; - transition: background-color 0.2s; -} - -.minicard-checklist .checklist-menu:hover { - background-color: rgba(0, 0, 0, 0.1); -} - -.minicard-checklist .checklist-item { - font-size: 0.75em; - color: #666; - margin-bottom: 0.2vh; - display: flex; - align-items: flex-start; - gap: 0.3vw; - line-height: 1.2; - cursor: pointer; - padding: 0.2vh 0; - border-radius: 0.2vw; - transition: background-color 0.2s; -} - -.minicard-checklist .checklist-item:hover { - background-color: rgba(0, 0, 0, 0.05); -} - -.minicard-checklist .checklist-item.is-checked { - text-decoration: line-through; - color: #999; -} - -.minicard-checklist .checklist-item .check-box-unicode { - flex-shrink: 0; - font-size: 0.8em; - margin-top: 0.1vh; - transition: transform 0.2s; -} - -.minicard-checklist .checklist-item:hover .check-box-unicode { - transform: scale(1.1); -} - -.minicard-checklist .checklist-item .item-title { - flex: 1; - word-wrap: break-word; - overflow-wrap: break-word; + opacity: 0.7; } diff --git a/client/components/cards/minicard.jade b/client/components/cards/minicard.jade index c6a88bd24..4a5040e76 100644 --- a/client/components/cards/minicard.jade +++ b/client/components/cards/minicard.jade @@ -3,13 +3,10 @@ template(name="minicard") class="{{#if isLinkedCard}}linked-card{{/if}}" class="{{#if isLinkedBoard}}linked-board{{/if}}" class="{{#if colorClass}}minicard-{{colorClass}}{{/if}}") - if canMoveCard - if isTouchScreenOrShowDesktopDragHandles - .handle - i.fa.fa-arrows if canModifyCard - a.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") - i.fa.fa-bars + a.minicard-details-menu-with-handle.js-open-minicard-details-menu(title="{{_ 'cardDetailsActionsPopup-title'}}") ☰ + .handle + | ↕️ .dates if getReceived .date @@ -33,7 +30,7 @@ template(name="minicard") if hasActiveUploads .minicard-upload-progress .upload-progress-header - i.fa.fa-upload + | 📤 span {{_ 'uploading-files'}} ({{uploadCount}}) each uploads .upload-progress-item(class="{{#if $eq status 'error'}}upload-error{{/if}}") @@ -42,11 +39,11 @@ template(name="minicard") .upload-progress-fill(style="width: {{progress}}%") if $eq status 'error' .upload-progress-error - i.fa.fa-warning + | ⚠️ span {{_ 'upload-failed'}} else if $eq status 'completed' .upload-progress-success - i.fa.fa-check + | ✅ span {{_ 'upload-completed'}} .minicard-title @@ -58,15 +55,12 @@ template(name="minicard") | {{ parentCardName }} if isLinkedBoard a.js-linked-link - span.linked-icon - i.fa.fa-folder + span.linked-icon | 📁 else if isLinkedCard a.js-linked-link - span.linked-icon - i.fa.fa-id-card + span.linked-icon | 🃏 if getArchived - span.linked-icon.linked-archived - i.fa.fa-archive + span.linked-icon.linked-archived | 📦 +viewer if currentBoard.allowsCardNumber span.card-number @@ -147,53 +141,45 @@ template(name="minicard") if canModifyCard if comments.length .badge(title="{{_ 'card-comments-title' comments.length }}") - span.badge-icon.badge-comment.badge-text - i.fa.fa-comment-o + span.badge-icon.badge-comment.badge-text 💬 = ' ' = comments.length //span.badge-comment.badge-text - //| - {{_ 'comment'}} + //| {{_ 'comment'}} if getDescription unless currentBoard.allowsDescriptionTextOnMinicard .badge.badge-state-image-only(title=getDescription) - span.badge-icon - i.fa.fa-file-text-o + span.badge-icon 📝 if getVoteQuestion .badge.badge-state-image-only(title=getVoteQuestion) - span.badge-icon(class="{{#if voteState}}text-green{{/if}}") - i.fa.fa-thumbs-up + span.badge-icon(class="{{#if voteState}}text-green{{/if}}") 👍 span.badge-text {{ voteCountPositive }} - span.badge-icon(class="{{#if $eq voteState false}}text-red{{/if}}") - i.fa.fa-thumbs-down + span.badge-icon(class="{{#if $eq voteState false}}text-red{{/if}}") 👎 span.badge-text {{ voteCountNegative }} if getPokerQuestion .badge.badge-state-image-only(title=getPokerQuestion) - span.badge-icon(class="{{#if pokerState}}text-green{{/if}}") - i.fa.fa-check-square + span.badge-icon(class="{{#if pokerState}}text-green{{/if}}") ✅ if expiredPoker span.badge-text {{ getPokerEstimation }} if attachments.length if currentBoard.allowsBadgeAttachmentOnMinicard .badge - span.badge-icon - i.fa.fa-paperclip + span.badge-icon 📎 span.badge-text= attachments.length + if checklists.length + .badge(class="{{#if checklistFinished}}is-finished{{/if}}") + span.badge-icon ☑️ + span.badge-text.check-list-text {{checklistFinishedCount}}/{{checklistItemCount}} if allSubtasks.count .badge - span.badge-icon - i.fa.fa-globe + span.badge-icon 🌐 span.badge-text.check-list-text {{subtasksFinishedCount}}/{{allSubtasksCount}} //{{subtasksFinishedCount}}/{{subtasksCount}} does not work because when a subtaks is archived, the count goes down if currentBoard.allowsCardSortingByNumber if currentBoard.allowsCardSortingByNumberOnMinicard .badge - span.badge-icon - i.fa.fa-sort-numeric-asc + span.badge-icon 🔢 span.badge-text.check-list-sort {{ sort }} - if shouldShowChecklistAtMinicard - each shouldShowChecklistAtMinicard - +minicardChecklist(checklist=. card=..) if currentBoard.allowsDescriptionTextOnMinicard if getDescription .minicard-description @@ -201,7 +187,7 @@ template(name="minicard") | {{ getDescription }} if shouldShowListOnMinicard .minicard-list-name - i.fa.fa-list + | 📋 | {{ listName }} if $eq 'subtext-with-full-path' currentBoard.presentParentTask .parent-subtext @@ -215,13 +201,55 @@ template(name="editCardSortOrderPopup") .edit-controls.clearfix button.primary.confirm.js-submit-edit-card-sort-popup(type="submit") {{_ 'save'}} -template(name="minicardChecklist") - .minicard-checklist - .checklist-header - .checklist-title= checklist.title - if canModifyCard - a.checklist-menu.js-open-checklist-menu(title="{{_ 'checklistActionsPopup-title'}}") - i.fa.fa-bars - each visibleItems - +checklistItemDetail(item = . checklist = checklist card = card) +template(name="minicardDetailsActionsPopup") + ul.pop-over-list + if canModifyCard + li + a.js-move-card + | ➡️ + | {{_ 'moveCardPopup-title'}} + li + a.js-copy-card + | 📋 + | {{_ 'copyCardPopup-title'}} + hr + li + a.js-archive + | ➡️ + | 📦 + | {{_ 'archive-card'}} + hr + li + a.js-move-card-to-top + | ⬆️ + | {{_ 'moveCardToTop-title'}} + li + a.js-move-card-to-bottom + | ⬇️ + | {{_ 'moveCardToBottom-title'}} + hr + li + a.js-add-labels + | 🏷️ + | {{_ 'card-edit-labels'}} + li + a.js-due-date + | 📥 + | {{_ 'editCardDueDatePopup-title'}} + li + a.js-set-card-color + | 🎨 + | {{_ 'setCardColorPopup-title'}} + li + a.js-link + | 🔗 + | {{_ 'link-card'}} + li + a.js-toggle-watch-card + if isWatching + | 👁️ + | {{_ 'unwatch'}} + else + | 👁️-slash + | {{_ 'watch'}} diff --git a/client/components/cards/minicard.js b/client/components/cards/minicard.js index 55bbf6cb9..91ebddc8c 100644 --- a/client/components/cards/minicard.js +++ b/client/components/cards/minicard.js @@ -8,9 +8,13 @@ import uploadProgressManager from '../../lib/uploadProgressManager'; // 'click .member': Popup.open('cardMember') // }); -Template.minicard.helpers({ +BlazeComponent.extendComponent({ + template() { + return 'minicard'; + }, + formattedCurrencyCustomFieldValue(definition) { - const customField = this + const customField = this.data() .customFieldsWD() .find(f => f._id === definition._id); const customFieldTrueValue = @@ -24,7 +28,7 @@ Template.minicard.helpers({ }, formattedStringtemplateCustomFieldValue(definition) { - const customField = this + const customField = this.data() .customFieldsWD() .find(f => f._id === definition._id); @@ -37,7 +41,7 @@ Template.minicard.helpers({ showCreatorOnMinicard() { // cache "board" to reduce the mini-mongodb access - const board = this.board(); + const board = this.data().board(); let ret = false; if (board) { ret = board.allowsCreatorOnMinicard ?? false; @@ -45,12 +49,13 @@ Template.minicard.helpers({ return ret; }, isWatching() { - return this.findWatcher(Meteor.userId()); + const card = this.currentData(); + return card.findWatcher(Meteor.userId()); }, showMembers() { // cache "board" to reduce the mini-mongodb access - const board = this.board(); + const board = this.data().board(); let ret = false; if (board) { ret = @@ -64,7 +69,7 @@ Template.minicard.helpers({ showAssignee() { // cache "board" to reduce the mini-mongodb access - const board = this.board(); + const board = this.data().board(); let ret = false; if (board) { ret = @@ -76,6 +81,96 @@ Template.minicard.helpers({ return ret; }, + /** opens the card label popup only if clicked onto a label + *
  • this is necessary to have the data context of the minicard. + * if .js-card-label is used at click event, then only the data context of the label itself is available at this.currentData() + */ + cardLabelsPopup(event) { + if (this.find('.js-card-label:hover')) { + Popup.open("cardLabels")(event, {dataContextIfCurrentDataIsUndefined: this.currentData()}); + } + }, + + events() { + return [ + { + 'click .js-linked-link'() { + if (this.data().isLinkedCard()) Utils.goCardId(this.data().linkedId); + else if (this.data().isLinkedBoard()) + Utils.goBoardId(this.data().linkedId); + }, + 'click .js-toggle-minicard-label-text'() { + if (window.localStorage.getItem('hiddenMinicardLabelText')) { + window.localStorage.removeItem('hiddenMinicardLabelText'); //true + } else { + window.localStorage.setItem('hiddenMinicardLabelText', 'true'); //true + } + }, + 'click span.badge-icon.fa.fa-sort, click span.badge-text.check-list-sort' : Popup.open("editCardSortOrder"), + 'click .minicard-labels' : this.cardLabelsPopup, + 'click .js-open-minicard-details-menu': Popup.open('minicardDetailsActions'), + // Drag and drop file upload handlers + 'dragover .minicard'(event) { + // Only prevent default for file drags to avoid interfering with sortable + const dataTransfer = event.originalEvent.dataTransfer; + if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { + event.preventDefault(); + event.stopPropagation(); + } + }, + 'dragenter .minicard'(event) { + const dataTransfer = event.originalEvent.dataTransfer; + if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { + event.preventDefault(); + event.stopPropagation(); + const card = this.data(); + const board = card.board(); + // Only allow drag-and-drop if user can modify card and board allows attachments + if (Utils.canModifyCard() && board && board.allowsAttachments) { + $(event.currentTarget).addClass('is-dragging-over'); + } + } + }, + 'dragleave .minicard'(event) { + const dataTransfer = event.originalEvent.dataTransfer; + if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { + event.preventDefault(); + event.stopPropagation(); + $(event.currentTarget).removeClass('is-dragging-over'); + } + }, + 'drop .minicard'(event) { + const dataTransfer = event.originalEvent.dataTransfer; + if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { + event.preventDefault(); + event.stopPropagation(); + $(event.currentTarget).removeClass('is-dragging-over'); + + const card = this.data(); + const board = card.board(); + + // Check permissions + if (!Utils.canModifyCard() || !board || !board.allowsAttachments) { + return; + } + + // Check if this is a file drop (not a card reorder) + if (!dataTransfer.files || dataTransfer.files.length === 0) { + return; + } + + const files = dataTransfer.files; + if (files && files.length > 0) { + handleFileUpload(card, files); + } + } + }, + } + ]; + }, +}).register('minicard'); + +Template.minicard.helpers({ hiddenMinicardLabelText() { const currentUser = ReactiveCache.getCurrentUser(); if (currentUser) { @@ -92,6 +187,9 @@ Template.minicard.helpers({ ? Meteor.connection._lastSessionId : null; }, + isWatching() { + return this.findWatcher(Meteor.userId()); + }, // Upload progress helpers hasActiveUploads() { return uploadProgressManager.hasActiveUploads(this._id); @@ -111,161 +209,68 @@ Template.minicard.helpers({ // Show list name if either: // 1. Board-wide setting is enabled, OR // 2. This specific card has the setting enabled - const currentBoard = this.board(); + const currentBoard = this.currentBoard; if (!currentBoard) return false; return currentBoard.allowsShowListsOnMinicard || this.showListOnMinicard; - }, - - shouldShowChecklistAtMinicard() { - // Return checklists that should be shown on minicard - const currentBoard = this.board(); - if (!currentBoard) return []; - - const checklists = this.checklists(); - const visibleChecklists = []; - - checklists.forEach(checklist => { - // Show checklist if either: - // 1. Board-wide setting is enabled, OR - // 2. This specific checklist has the setting enabled - if (currentBoard.allowsChecklistAtMinicard || checklist.showChecklistAtMinicard) { - visibleChecklists.push(checklist); - } - }); - - return visibleChecklists; } }); -Template.minicard.events({ - 'click .js-linked-link'() { - if (this.isLinkedCard()) Utils.goCardId(this.linkedId); - else if (this.isLinkedBoard()) - Utils.goBoardId(this.linkedId); - }, - 'click .js-toggle-minicard-label-text'() { - if (window.localStorage.getItem('hiddenMinicardLabelText')) { - window.localStorage.removeItem('hiddenMinicardLabelText'); //true - } else { - window.localStorage.setItem('hiddenMinicardLabelText', 'true'); //true - } - }, - 'click span.badge-icon.fa.fa-sort, click span.badge-text.check-list-sort' : Popup.open("editCardSortOrder"), - 'click .minicard-labels'(event, tpl) { - if (tpl.find('.js-card-label:hover')) { - Popup.open("cardLabels")(event, {dataContextIfCurrentDataIsUndefined: Template.currentData()}); - } - }, - 'click .js-open-minicard-details-menu'(event, tpl) { +BlazeComponent.extendComponent({ + events() { + return [ + { + 'keydown input.js-edit-card-sort-popup'(evt) { + // enter = save + if (evt.keyCode === 13) { + this.find('button[type=submit]').click(); + } + }, + 'click button.js-submit-edit-card-sort-popup'(event) { + // save button pressed + event.preventDefault(); + const sort = this.$('.js-edit-card-sort-popup')[0] + .value + .trim(); + if (!Number.isNaN(sort)) { + let card = this.data(); + card.move(card.boardId, card.swimlaneId, card.listId, sort); + Popup.back(); + } + }, + } + ] + } +}).register('editCardSortOrderPopup'); + +Template.minicardDetailsActionsPopup.events({ + 'click .js-due-date': Popup.open('editCardDueDate'), + 'click .js-move-card': Popup.open('moveCard'), + 'click .js-copy-card': Popup.open('copyCard'), + 'click .js-set-card-color': Popup.open('setCardColor'), + 'click .js-add-labels': Popup.open('cardLabels'), + 'click .js-link': Popup.open('linkCard'), + 'click .js-move-card-to-top'(event) { event.preventDefault(); - event.stopPropagation(); - const card = Template.currentData(); - Popup.open('cardDetailsActions').call({currentData: () => card}, event); + const minOrder = this.getMinSort(); + this.move(this.boardId, this.swimlaneId, this.listId, minOrder - 1); + Popup.back(); }, - // Drag and drop file upload handlers - 'dragover .minicard'(event) { - // Only prevent default for file drags to avoid interfering with sortable - const dataTransfer = event.originalEvent.dataTransfer; - if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { - event.preventDefault(); - event.stopPropagation(); - } + 'click .js-move-card-to-bottom'(event) { + event.preventDefault(); + const maxOrder = this.getMaxSort(); + this.move(this.boardId, this.swimlaneId, this.listId, maxOrder + 1); + Popup.back(); }, - 'dragenter .minicard'(event) { - const dataTransfer = event.originalEvent.dataTransfer; - if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { - event.preventDefault(); - event.stopPropagation(); - const card = this; - const board = card.board(); - // Only allow drag-and-drop if user can modify card and board allows attachments - if (Utils.canModifyCard() && board && board.allowsAttachments) { - $(event.currentTarget).addClass('is-dragging-over'); - } - } - }, - 'dragleave .minicard'(event) { - const dataTransfer = event.originalEvent.dataTransfer; - if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { - event.preventDefault(); - event.stopPropagation(); - $(event.currentTarget).removeClass('is-dragging-over'); - } - }, - 'drop .minicard'(event) { - const dataTransfer = event.originalEvent.dataTransfer; - if (dataTransfer && dataTransfer.types && dataTransfer.types.includes('Files')) { - event.preventDefault(); - event.stopPropagation(); - $(event.currentTarget).removeClass('is-dragging-over'); - - const card = this; - const board = card.board(); - - // Check permissions - if (!Utils.canModifyCard() || !board || !board.allowsAttachments) { - return; - } - - // Check if this is a file drop (not a card reorder) - if (!dataTransfer.files || dataTransfer.files.length === 0) { - return; - } - - const files = dataTransfer.files; - if (files && files.length > 0) { - handleFileUpload(card, files); - } - } - }, -}); - -Template.minicardChecklist.helpers({ - visibleItems() { - const checklist = this.checklist || this; - const items = checklist.items(); - - return items.filter(item => { - // Hide finished items if hideCheckedChecklistItems is true - if (item.isFinished && checklist.hideCheckedChecklistItems) { - return false; - } - // Hide all items if hideAllChecklistItems is true - if (checklist.hideAllChecklistItems) { - return false; - } - return true; + 'click .js-archive': Popup.afterConfirm('cardArchive', function () { + Popup.close(); + this.archive(); + Utils.goBoardId(this.boardId); + }), + 'click .js-toggle-watch-card'() { + const currentCard = this; + const level = currentCard.findWatcher(Meteor.userId()) ? null : 'watching'; + Meteor.call('watch', 'card', currentCard._id, level, (err, ret) => { + if (!err && ret) Popup.back(); }); }, }); - -Template.minicardChecklist.events({ - 'click .js-open-checklist-menu'(event) { - const data = Template.currentData(); - const checklist = data.checklist || data; - const card = data.card || this; - const context = { currentData: () => ({ checklist, card }) }; - Popup.open('checklistActions').call(context, event); - }, -}); - -Template.editCardSortOrderPopup.events({ - 'keydown input.js-edit-card-sort-popup'(evt, tpl) { - // enter = save - if (evt.keyCode === 13) { - tpl.find('button[type=submit]').click(); - } - }, - 'click button.js-submit-edit-card-sort-popup'(event, tpl) { - // save button pressed - event.preventDefault(); - const sort = tpl.$('.js-edit-card-sort-popup')[0] - .value - .trim(); - if (!Number.isNaN(sort)) { - let card = this; - card.move(card.boardId, card.swimlaneId, card.listId, sort); - Popup.back(); - } - }, -}); diff --git a/client/components/cards/resultCard.jade b/client/components/cards/resultCard.jade index 90d7c07a0..f2bd16657 100644 --- a/client/components/cards/resultCard.jade +++ b/client/components/cards/resultCard.jade @@ -11,9 +11,9 @@ template(name="resultCard") = getBoard.title else .broken-cards-null - | {{_ 'no-name'}} + | NULL if getBoard.archived - i.fa.fa-archive + | 📦 li.result-card-context.result-card-context-separator = ' ' | {{_ 'context-separator'}} @@ -25,9 +25,9 @@ template(name="resultCard") = getSwimlane.title else .broken-cards-null - | {{_ 'no-name'}} + | NULL if getSwimlane.archived - i.fa.fa-archive + | 📦 li.result-card-context.result-card-context-separator = ' ' | {{_ 'context-separator'}} @@ -39,6 +39,6 @@ template(name="resultCard") = getList.title else .broken-cards-null - | {{_ 'no-name'}} + | NULL if getList.archived - i.fa.fa-archive + | 📦 diff --git a/client/components/cards/resultCard.js b/client/components/cards/resultCard.js index 78bc9acd2..8e04f1654 100644 --- a/client/components/cards/resultCard.js +++ b/client/components/cards/resultCard.js @@ -4,19 +4,32 @@ Template.resultCard.helpers({ }, }); -Template.resultCard.events({ - 'click .js-minicard'(event) { - event.preventDefault(); - const cardId = Template.currentData()._id; - const boardId = Template.currentData().boardId; +BlazeComponent.extendComponent({ + clickOnMiniCard(evt) { + evt.preventDefault(); + const this_ = this; + const cardId = this.currentData()._id; + const boardId = this.currentData().boardId; Meteor.subscribe('popupCardData', cardId, { onReady() { Session.set('popupCardId', cardId); Session.set('popupCardBoardId', boardId); - if (!Popup.isOpen()) { - Popup.open("cardDetails")(event); - } + this_.cardDetailsPopup(evt); }, }); }, -}); + + cardDetailsPopup(event) { + if (!Popup.isOpen()) { + Popup.open("cardDetails")(event); + } + }, + + events() { + return [ + { + 'click .js-minicard': this.clickOnMiniCard, + }, + ]; + }, +}).register('resultCard'); diff --git a/client/components/cards/subtasks.css b/client/components/cards/subtasks.css index ba89ad2b0..08f5122c2 100644 --- a/client/components/cards/subtasks.css +++ b/client/components/cards/subtasks.css @@ -87,15 +87,6 @@ textarea.js-edit-subtask-item { top: 0; bottom: -600px; right: 0; - z-index: 15; -} - -/* Fix for mobile Safari: ensure this doesn't block card interaction */ -@media screen and (max-width: 800px) { - #card-details-overlay { - z-index: 15; - pointer-events: none; - } } .subtasks { background: #f7f7f7; @@ -136,25 +127,6 @@ textarea.js-edit-subtask-item { border-bottom: 2px solid #3cb500; border-right: 2px solid #3cb500; } -/* Unicode checkbox icons styling */ -.subtasks-item .check-box-unicode { - font-size: 1.3em; - margin-right: 8px; - cursor: pointer; - display: inline-block; - vertical-align: middle; - line-height: 1; -} -/* Grey checkmarks when grey icons setting is enabled */ -body.grey-icons-enabled .subtasks-item .check-box.is-checked { - border-bottom: 2px solid #7a7a7a; - border-right: 2px solid #7a7a7a; -} -body.grey-icons-enabled .subtasks-item .check-box-unicode { - filter: grayscale(100%); - -webkit-filter: grayscale(100%); - opacity: 0.85; -} .subtasks-item .item-title { flex: 1; padding-left: 10px; diff --git a/client/components/cards/subtasks.jade b/client/components/cards/subtasks.jade index f8e711c28..ceb860e6e 100644 --- a/client/components/cards/subtasks.jade +++ b/client/components/cards/subtasks.jade @@ -1,6 +1,6 @@ template(name="subtasks") h3.card-details-item-title - i.fa.fa-globe + | 🌐 | {{_ 'subtasks'}} if currentUser.isBoardAdmin if toggleDeleteDialog.get @@ -16,7 +16,7 @@ template(name="subtasks") +addSubtaskItemForm else a.js-open-inlined-form(title="{{_ 'add-subtask'}}") - i.fa.fa-plus + | ➕ template(name="subtaskDetail") .js-subtasks.subtask @@ -51,7 +51,7 @@ template(name="editSubtaskItemForm") .edit-controls.clearfix button.primary.confirm.js-submit-edit-subtask-item-form(type="submit") {{_ 'save'}} a.js-close-inlined-form - span(title=createdAt) {{ displayDate createdAt }} + span(title=createdAt) {{ moment createdAt }} if canModifyCard if currentUser.isBoardAdmin a.js-delete-subtask-item {{_ "delete"}}... @@ -68,20 +68,18 @@ template(name="subtasksItems") +addSubtaskItemForm else a.add-subtask-item.js-open-inlined-form - i.fa.fa-plus + | ➕ | {{_ 'add-subtask-item'}}... template(name='subtaskItemDetail') .js-subtasks-item.subtasks-item if canModifyCard - span.check-box-unicode - i.fa(class="{{#if item.isFinished}}fa-check-square{{else}}fa-square-o{{/if}}") + .check-box.materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}") .item-title.js-open-inlined-form.is-editable(class="{{#if item.isFinished }}is-checked{{/if}}") +viewer = item.title else - span.check-box-unicode - i.fa(class="{{#if item.isFinished}}fa-check-square{{else}}fa-square-o{{/if}}") + .materialCheckBox(class="{{#if item.isFinished }}is-checked{{/if}}") .item-title(class="{{#if item.isFinished }}is-checked{{/if}}") +viewer = item.title @@ -94,10 +92,10 @@ template(name="subtaskActionsPopup") ul.pop-over-list li a.js-view-subtask(title="{{ subtask.title }}") - i.fa.fa-eye + | 👁️ | {{_ "view-it"}} if currentUser.isBoardAdmin a.js-delete-subtask.delete-subtask - i.fa.fa-trash + | 🗑️ | {{_ "delete"}} ... diff --git a/client/components/cards/subtasks.js b/client/components/cards/subtasks.js index 7cf0156e7..af5654802 100644 --- a/client/components/cards/subtasks.js +++ b/client/components/cards/subtasks.js @@ -1,13 +1,11 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; -Template.subtasks.events({ - 'click .js-open-subtask-details-menu': Popup.open('subtaskActions'), - 'submit .js-add-subtask'(event, tpl) { +BlazeComponent.extendComponent({ + addSubtask(event) { event.preventDefault(); - const textarea = tpl.find('textarea.js-add-subtask-item'); + const textarea = this.find('textarea.js-add-subtask-item'); const title = textarea.value.trim(); - const cardId = Template.currentData().cardId; + const cardId = this.currentData().cardId; const card = ReactiveCache.getCard(cardId); const sortIndex = -1; const crtBoard = ReactiveCache.getBoard(card.boardId); @@ -54,7 +52,7 @@ Template.subtasks.events({ Filter.addException(_id); setTimeout(() => { - tpl.$('.add-subtask-item') + this.$('.add-subtask-item') .last() .click(); }, 100); @@ -62,20 +60,27 @@ Template.subtasks.events({ textarea.value = ''; textarea.focus(); }, - 'submit .js-edit-subtask-title'(event, tpl) { - event.preventDefault(); - const textarea = tpl.find('textarea.js-edit-subtask-item'); - const title = textarea.value.trim(); - const subtask = Template.currentData().subtask; - subtask.setTitle(title); - }, - async 'click .js-delete-subtask-item'() { - const subtask = Template.currentData().subtask; + + deleteSubtask() { + const subtask = this.currentData().subtask; if (subtask && subtask._id) { - await subtask.archive(); + subtask.archive(); } }, - keydown(event) { + + isBoardAdmin() { + return ReactiveCache.getCurrentUser().isBoardAdmin(); + }, + + editSubtask(event) { + event.preventDefault(); + const textarea = this.find('textarea.js-edit-subtask-item'); + const title = textarea.value.trim(); + const subtask = this.currentData().subtask; + subtask.setTitle(title); + }, + + pressKey(event) { //If user press enter key inside a form, submit it //Unless the user is also holding down the 'shift' key if (event.keyCode === 13 && !event.shiftKey) { @@ -84,58 +89,53 @@ Template.subtasks.events({ $form.find('button[type=submit]').click(); } }, -}); -Template.subtasks.onCreated(function () { - this.toggleDeleteDialog = new ReactiveVar(false); -}); + events() { + return [ + { + 'click .js-open-subtask-details-menu': Popup.open('subtaskActions'), + 'submit .js-add-subtask': this.addSubtask, + 'submit .js-edit-subtask-title': this.editSubtask, + 'click .js-delete-subtask-item': this.deleteSubtask, + keydown: this.pressKey, + }, + ]; + }, +}).register('subtasks'); -Template.subtasks.helpers({ +BlazeComponent.extendComponent({ + // ... +}).register('subtaskItemDetail'); + +BlazeComponent.extendComponent({ isBoardAdmin() { return ReactiveCache.getCurrentUser().isBoardAdmin(); }, - toggleDeleteDialog() { - return Template.instance().toggleDeleteDialog; - }, -}); - -Template.subtaskItemDetail.events({ - async 'click .js-subtasks-item .check-box-unicode'() { - const item = Template.currentData().item; - if (item && item._id) { - await item.toggleItem(); - } - }, -}); - -Template.subtaskActionsPopup.helpers({ - isBoardAdmin() { - return ReactiveCache.getCurrentUser().isBoardAdmin(); - }, -}); - -Template.subtaskActionsPopup.events({ - 'click .js-view-subtask'(event) { - if ($(event.target).hasClass('js-view-subtask')) { - const subtask = Template.currentData().subtask; - const board = subtask.board(); - FlowRouter.go('card', { - boardId: board._id, - slug: board.slug, - cardId: subtask._id, - swimlaneId: subtask.swimlaneId, - listId: subtask.listId, - }); - } - }, - 'click .js-delete-subtask' : Popup.afterConfirm('subtaskDelete', async function () { - Popup.back(2); - const subtask = this.subtask; - if (subtask && subtask._id) { - await subtask.archive(); - } - }), -}); + events() { + return [ + { + 'click .js-view-subtask'(event) { + if ($(event.target).hasClass('js-view-subtask')) { + const subtask = this.currentData().subtask; + const board = subtask.board(); + FlowRouter.go('card', { + boardId: board._id, + slug: board.slug, + cardId: subtask._id, + }); + } + }, + 'click .js-delete-subtask' : Popup.afterConfirm('subtaskDelete', function () { + Popup.back(2); + const subtask = this.subtask; + if (subtask && subtask._id) { + subtask.archive(); + } + }), + } + ] + } +}).register('subtaskActionsPopup'); Template.editSubtaskItemForm.helpers({ user() { @@ -145,3 +145,5 @@ Template.editSubtaskItemForm.helpers({ return ReactiveCache.getCurrentUser().isBoardAdmin(); }, }); + + diff --git a/client/components/common/originalPosition.css b/client/components/common/originalPosition.css index 9dc64da40..1c31c4860 100644 --- a/client/components/common/originalPosition.css +++ b/client/components/common/originalPosition.css @@ -81,11 +81,11 @@ font-size: 11px; padding: 6px; } - + .original-position-details { padding: 4px 6px; } - + .original-position-moved, .original-position-unchanged { padding: 3px 5px; @@ -99,24 +99,24 @@ border-color: #4a5568; color: #e2e8f0; } - + .original-position-moved { background-color: #744210; border-color: #b7791f; color: #fbd38d; } - + .original-position-unchanged { background-color: #22543d; border-color: #38a169; color: #9ae6b4; } - + .original-title { color: #a0aec0; border-color: #4a5568; } - + .original-title strong { color: #e2e8f0; } diff --git a/client/components/common/originalPosition.html b/client/components/common/originalPosition.html new file mode 100644 index 000000000..3e3191e27 --- /dev/null +++ b/client/components/common/originalPosition.html @@ -0,0 +1,29 @@ + diff --git a/client/components/common/originalPosition.jade b/client/components/common/originalPosition.jade deleted file mode 100644 index 37a8771a2..000000000 --- a/client/components/common/originalPosition.jade +++ /dev/null @@ -1,19 +0,0 @@ -template(name="originalPosition") - .original-position-info - if isLoading - .original-position-loading - | ⏳ Loading original position... - else - if showOriginalPosition - .original-position-details - if hasMovedFromOriginal - .original-position-moved - span.original-position-text ℹ️ {{getOriginalPositionDescription}} - else - .original-position-unchanged - span.original-position-text ✅ In original position - - if getOriginalTitle - .original-title - strong Original title: - | {{getOriginalTitle}} diff --git a/client/components/common/originalPosition.js b/client/components/common/originalPosition.js index 742015b65..37e0a4522 100644 --- a/client/components/common/originalPosition.js +++ b/client/components/common/originalPosition.js @@ -1,71 +1,71 @@ +import { BlazeComponent } from 'meteor/peerlibrary:blaze-components'; import { ReactiveVar } from 'meteor/reactive-var'; import { Meteor } from 'meteor/meteor'; +import { Template } from 'meteor/templating'; +import './originalPosition.html'; /** * Component to display original position information for swimlanes, lists, and cards */ +class OriginalPositionComponent extends BlazeComponent { + onCreated() { + super.onCreated(); + this.originalPosition = new ReactiveVar(null); + this.isLoading = new ReactiveVar(false); + this.hasMoved = new ReactiveVar(false); + + this.autorun(() => { + const data = this.data(); + if (data && data.entityId && data.entityType) { + this.loadOriginalPosition(data.entityId, data.entityType); + } + }); + } -Template.originalPosition.onCreated(function () { - this.originalPosition = new ReactiveVar(null); - this.isLoading = new ReactiveVar(false); - this.hasMoved = new ReactiveVar(false); - - const tpl = this; - - function loadOriginalPosition(entityId, entityType) { - tpl.isLoading.set(true); - + loadOriginalPosition(entityId, entityType) { + this.isLoading.set(true); + const methodName = `positionHistory.get${entityType.charAt(0).toUpperCase() + entityType.slice(1)}OriginalPosition`; - + Meteor.call(methodName, entityId, (error, result) => { - tpl.isLoading.set(false); + this.isLoading.set(false); if (error) { console.error('Error loading original position:', error); - tpl.originalPosition.set(null); + this.originalPosition.set(null); } else { - tpl.originalPosition.set(result); - + this.originalPosition.set(result); + // Check if the entity has moved const movedMethodName = `positionHistory.has${entityType.charAt(0).toUpperCase() + entityType.slice(1)}Moved`; Meteor.call(movedMethodName, entityId, (movedError, movedResult) => { if (!movedError) { - tpl.hasMoved.set(movedResult); + this.hasMoved.set(movedResult); } }); } }); } - this.autorun(() => { - const data = Template.currentData(); - if (data && data.entityId && data.entityType) { - loadOriginalPosition(data.entityId, data.entityType); - } - }); -}); - -Template.originalPosition.helpers({ getOriginalPosition() { - return Template.instance().originalPosition.get(); - }, + return this.originalPosition.get(); + } isLoading() { - return Template.instance().isLoading.get(); - }, + return this.isLoading.get(); + } hasMovedFromOriginal() { - return Template.instance().hasMoved.get(); - }, + return this.hasMoved.get(); + } getOriginalPositionDescription() { - const position = Template.instance().originalPosition.get(); + const position = this.getOriginalPosition(); if (!position) return 'No original position data'; - + if (position.originalPosition) { - const data = Template.currentData(); - const entityType = data.entityType; + const entityType = this.data().entityType; let description = `Original position: ${position.originalPosition.sort || 0}`; - + if (entityType === 'list' && position.originalSwimlaneId) { description += ` in swimlane ${position.originalSwimlaneId}`; } else if (entityType === 'card') { @@ -76,19 +76,23 @@ Template.originalPosition.helpers({ description += ` in list ${position.originalListId}`; } } - + return description; } - + return 'No original position data'; - }, + } getOriginalTitle() { - const position = Template.instance().originalPosition.get(); + const position = this.getOriginalPosition(); return position ? position.originalTitle : ''; - }, + } showOriginalPosition() { - return Template.instance().originalPosition.get() !== null; - }, -}); + return this.getOriginalPosition() !== null; + } +} + +OriginalPositionComponent.register('originalPosition'); + +export default OriginalPositionComponent; diff --git a/client/components/forms/datepicker.jade b/client/components/forms/datepicker.jade index c8fb0524a..1fbdb2383 100644 --- a/client/components/forms/datepicker.jade +++ b/client/components/forms/datepicker.jade @@ -4,7 +4,7 @@ template(name="datepicker") .fields .left label(for="date") {{_ 'date'}} - input.js-date-field#date(type="date" name="date" value=showDate autofocus) + input.js-date-field#date(type="text" name="date" value=showDate autofocus placeholder=dateFormat) .right label(for="time") {{_ 'time'}} input.js-time-field#time(type="time" name="time" value=showTime) diff --git a/client/components/forms/datepicker.js b/client/components/forms/datepicker.js deleted file mode 100644 index c1b60025c..000000000 --- a/client/components/forms/datepicker.js +++ /dev/null @@ -1,16 +0,0 @@ -import { - setupDatePicker, - datePickerRendered, - datePickerHelpers, - datePickerEvents, -} from '/client/lib/datepicker'; - -Template.datepicker.onCreated(function () { - setupDatePicker(this); -}); - -Template.datepicker.onRendered(function () { - datePickerRendered(this); -}); - -Template.datepicker.helpers(datePickerHelpers()); diff --git a/client/components/forms/forms.css b/client/components/forms/forms.css index ed26361bf..3b1566514 100644 --- a/client/components/forms/forms.css +++ b/client/components/forms/forms.css @@ -130,8 +130,8 @@ textarea.editor { } input[type="submit"], button { - background: #000; - background: linear-gradient(#000, #000); + background: #cfcfcf; + background: linear-gradient(#cfcfcf, #c2c2c2); border: none; cursor: pointer; display: inline-block; @@ -139,7 +139,6 @@ button { line-height: 1.3; padding: 1vh 2.5vw; text-align: center; - color: #fff; } input[type="submit"] .wide, button .wide { @@ -150,16 +149,14 @@ input[type="submit"]:hover, button:hover, input[type="submit"]:focus, button:focus { - background: #222; - background: linear-gradient(#222, #222); - color: #fff; + background: #c2c2c2; + background: linear-gradient(#c2c2c2, #b5b5b5); } input[type="submit"]:active, button:active { - background: #111; - background: linear-gradient(#111, #111); - box-shadow: inset 0 3px 6px rgba(0,0,0,0.3); - color: #fff; + background: #b5b5b5; + background: linear-gradient(#b5b5b5, #a8a8a8); + box-shadow: inset 0 3px 6px rgba(0,0,0,0.1); } input[type="submit"]:active:hover, button:active:hover, @@ -186,12 +183,6 @@ input[type="submit"].primary:active, button.primary:active { background: #01628c; } -input[type="submit"].negate, -button.negate { - background: #eb5a46; - box-shadow: 0 1px 0 #4d4d4d; - color: #fff; -} input[type="submit"].negate:hover, button.negate:hover, input[type="submit"].negate:focus, @@ -226,10 +217,10 @@ input[type="submit"]:disabled:active, input[type="button"].disabled:active, button.disabled:active, .button.disabled:active { - background: #555; + background: #cfcfcf; cursor: default; box-shadow: none; - color: #999; + color: #a8a8a8; } fieldset { border: 1px solid #bfbfbf; @@ -324,18 +315,11 @@ textarea::-moz-placeholder { margin-right: 6px; border-top: 2px solid transparent; border-left: 2px solid transparent; - border-bottom: 2px solid #3cb500; - border-right: 2px solid #3cb500; transform: rotate(40deg); -webkit-backface-visibility: hidden; backface-visibility: hidden; transform-origin: 100% 100%; } -/* Grey checkmarks when grey icons setting is enabled */ -body.grey-icons-enabled .materialCheckBox.is-checked { - border-bottom: 2px solid #7a7a7a; - border-right: 2px solid #7a7a7a; -} .button-link { background: #fff; background: linear-gradient(#fff, #f5f5f5); @@ -409,12 +393,12 @@ body.grey-icons-enabled .materialCheckBox.is-checked { .button-link.setting.disabled.primary, .button-link.setting.disabled.primary:hover, .button-link.setting.disabled.primary:active { - background: #555; - border-color: #444; - border-bottom-color: #333; + background: #cfcfcf; + border-color: #c2c2c2; + border-bottom-color: #b5b5b5; cursor: default; box-shadow: none; - color: #999; + color: #a8a8a8; } .button-link.setting .label { color: #222; diff --git a/client/components/gantt/gantt.css b/client/components/gantt/gantt.css deleted file mode 100644 index 81139f07b..000000000 --- a/client/components/gantt/gantt.css +++ /dev/null @@ -1,203 +0,0 @@ -/* Gantt chart cell background colors for Received, Start, Due, End (matching cardDetails) */ -.ganttview-received { - background-color: #dbdbdb !important; - color: #000 !important; - font-size: 18px !important; - font-weight: bold !important; -} -.ganttview-start { - background-color: #90ee90 !important; - color: #000 !important; - font-size: 18px !important; - font-weight: bold !important; -} -.ganttview-due { - background-color: #ffd700 !important; - color: #000 !important; - font-size: 18px !important; - font-weight: bold !important; -} -.ganttview-end { - background-color: #ffb3b3 !important; - color: #000 !important; - font-size: 18px !important; - font-weight: bold !important; -} -/* Gantt View Styles */ - -.gantt-view { - width: 100%; - height: auto; - overflow: visible; - background-color: #fff; -} - -.gantt-view.swimlane { - background-color: #fff; - padding: 10px; -} - -.gantt-container { - overflow-x: auto; - overflow-y: visible; - background-color: #fff; - display: block; - width: 100%; -} - -.gantt-container table, -.gantt-table { - border-collapse: collapse; - width: 100%; - min-width: 800px; - border: 2px solid #666; - font-family: sans-serif; - font-size: 13px; - background-color: #fff; -} - -.gantt-container thead { - background-color: #e8e8e8; - border-bottom: 2px solid #666; - font-weight: bold; - position: sticky; - top: 0; - z-index: 10; -} - -.gantt-container thead th, -.gantt-container thead tr > td:first-child { - border-right: 2px solid #666; - padding: 4px; /* half of 8px */ - width: 100px; /* half of 200px */ - text-align: left; - font-weight: bold; - background-color: #e8e8e8; - min-width: 100px; /* half of 200px */ -} - -.gantt-container thead td { - border-right: 1px solid #999; - padding: 2px 1px; /* half */ - text-align: center; - background-color: #f5f5f5; - font-size: 11px; - min-width: 15px; /* half of 30px */ - font-weight: bold; - height: auto; - line-height: 1.2; - white-space: normal; - word-break: break-word; -} - -.gantt-container tbody tr { - border-bottom: 1px solid #999; - height: 32px; -} - -.gantt-container tbody tr:hover { - background-color: #f9f9f9; -} - -.gantt-container tbody tr:hover td { - background-color: #f9f9f9 !important; -} - -.gantt-container tbody td { - border-right: 1px solid #ccc; - padding: 1px; /* half */ - text-align: center; - min-width: 15px; /* half of 30px */ - height: 32px; - vertical-align: middle; - line-height: 28px; - background-color: #ffffff; - font-size: 18px; - font-weight: bold; -} - -.gantt-container tbody td:nth-child(even) { - background-color: #fafafa; -} - -.gantt-container tbody td:first-child { - border-right: 2px solid #666; - padding: 4px; /* half of 8px */ - font-weight: 500; - cursor: pointer; - background-color: #fafafa !important; - text-align: left; - width: 100px; /* half of 200px */ - min-width: 100px; /* half of 200px */ - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; - height: auto; - line-height: normal; -} - -.gantt-container tbody td:first-child:hover { - background-color: #f0f0f0 !important; - text-decoration: underline; -} - -.js-gantt-task-cell { - cursor: pointer; -} - -.js-gantt-date-icon { - cursor: pointer; -} - -.gantt-container .ganttview-weekend { - background-color: #efefef; -} - -.gantt-container .ganttview-today { - background-color: #fcf8e3; - border-right: 2px solid #ffb347; -} - -/* Task bar styling - VERY VISIBLE */ -.gantt-container tbody td.ganttview-block { - background-color: #4CAF50 !important; - color: #fff !important; - font-size: 18px !important; - font-weight: bold !important; - padding: 2px !important; - border-radius: 2px; -} - -/* Responsive adjustments */ -@media (max-width: 768px) { - .gantt-container table { - font-size: 11px; - } - - .gantt-container thead td { - min-width: 20px; - padding: 2px; - } - - .gantt-container tbody td { - min-width: 20px; - padding: 1px; - height: 20px; - } - - .gantt-container tbody td:first-child { - width: 100px; - font-size: 12px; - } -} - -/* Print styles */ -@media print { - .gantt-container { - overflow: visible; - } - - .gantt-container table { - page-break-inside: avoid; - } -} diff --git a/client/components/gantt/gantt.jade b/client/components/gantt/gantt.jade deleted file mode 100644 index cf841ed2e..000000000 --- a/client/components/gantt/gantt.jade +++ /dev/null @@ -1,27 +0,0 @@ -//- Gantt Chart View Template -template(name="ganttView") - link(rel="stylesheet" href="/client/components/gantt/gantt.css") - link(rel="stylesheet" href="/client/components/gantt/ganttCard.css") - .gantt-view - h2 {{_ 'board-view-gantt'}} - if hasSelectedCard - +ganttCard(selectedCard) - each weeks - table.gantt-table - thead - tr - th {{_ 'task'}} {{_ 'predicate-week'}} {{week}} - each weekDays this - th - | {{formattedDate .}} {{weekdayLabel .}} - tbody - each cardsInWeek this - tr(data-card-id="{{cardId .}}") - td.js-gantt-task-cell - a.js-gantt-card-title(href="#") - +viewer - | {{cardTitle .}} - each weekDays .. - td(class="{{cellClasses .. .}}" data-card-id="{{cardId ..}}" data-date-type="{{cellContentClass .. .}}") - | {{cellContent .. .}} - diff --git a/client/components/gantt/gantt.js b/client/components/gantt/gantt.js deleted file mode 100644 index d4299abbd..000000000 --- a/client/components/gantt/gantt.js +++ /dev/null @@ -1,219 +0,0 @@ -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; - -// Add click handler to ganttView for card titles -Template.ganttView.events({ - 'click .js-gantt-card-title'(event, template) { - event.preventDefault(); - // Get card ID from the closest row's data attribute - const $row = template.$(event.currentTarget).closest('tr'); - const cardId = $row.data('card-id'); - - if (cardId) { - template.selectedCardId.set(cardId); - } - }, -}); -import { Template } from 'meteor/templating'; - -// Blaze template helpers for ganttView -function getISOWeekInfo(d) { - const date = new Date(Date.UTC(d.getFullYear(), d.getMonth(), d.getDate())); - const dayNum = date.getUTCDay() || 7; - date.setUTCDate(date.getUTCDate() + 4 - dayNum); - const yearStart = new Date(Date.UTC(date.getUTCFullYear(), 0, 1)); - const week = Math.ceil((((date - yearStart) / 86400000) + 1) / 7); - return { year: date.getUTCFullYear(), week }; -} -function startOfISOWeek(d) { - const date = new Date(d); - const day = date.getDay() || 7; - if (day !== 1) date.setDate(date.getDate() - (day - 1)); - date.setHours(0,0,0,0); - return date; -} - -Template.ganttView.helpers({ - weeks() { - const board = Utils.getCurrentBoard(); - if (!board) return []; - const cards = Cards.find({ boardId: board._id }, { sort: { startAt: 1, dueAt: 1 } }).fetch(); - const weeksMap = new Map(); - const relevantCards = cards.filter(c => c.receivedAt || c.startAt || c.dueAt || c.endAt); - relevantCards.forEach(card => { - ['receivedAt','startAt','dueAt','endAt'].forEach(field => { - if (card[field]) { - const dt = new Date(card[field]); - const info = getISOWeekInfo(dt); - const key = `${info.year}-W${info.week}`; - if (!weeksMap.has(key)) { - weeksMap.set(key, { year: info.year, week: info.week, start: startOfISOWeek(dt) }); - } - } - }); - }); - return Array.from(weeksMap.values()).sort((a,b) => a.start - b.start); - }, - weekDays(week) { - const weekStart = new Date(week.start); - return Array.from({length:7}, (_,i) => { - const d = new Date(weekStart); - d.setDate(d.getDate() + i); - d.setHours(0,0,0,0); - return d; - }); - }, - weekdayLabel(day) { - const weekdayKeys = ['monday','tuesday','wednesday','thursday','friday','saturday','sunday']; - return TAPi18n.__(weekdayKeys[day.getDay() === 0 ? 6 : day.getDay() - 1]); - }, - formattedDate(day) { - const currentUser = ReactiveCache.getCurrentUser && ReactiveCache.getCurrentUser(); - const dateFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD'; - return formatDateByUserPreference(day, dateFormat, false); - }, - cardsInWeek(week) { - const board = Utils.getCurrentBoard(); - if (!board) return []; - const cards = Cards.find({ boardId: board._id }).fetch(); - return cards.filter(card => { - return ['receivedAt','startAt','dueAt','endAt'].some(field => { - if (card[field]) { - const dt = new Date(card[field]); - const info = getISOWeekInfo(dt); - return info.week === week.week && info.year === week.year; - } - return false; - }); - }); - }, - cardTitle(card) { - return card.title; - }, - cardId(card) { - return card._id; - }, - cardUrl(card) { - if (!card) return '#'; - const board = ReactiveCache.getBoard(card.boardId); - if (!board) return '#'; - return FlowRouter.path('card', { - boardId: card.boardId, - slug: board.slug, - cardId: card._id, - }); - }, - cellContentClass(card, day) { - const cardDates = { - receivedAt: card.receivedAt ? new Date(card.receivedAt) : null, - startAt: card.startAt ? new Date(card.startAt) : null, - dueAt: card.dueAt ? new Date(card.dueAt) : null, - endAt: card.endAt ? new Date(card.endAt) : null, - }; - if (cardDates.receivedAt && cardDates.receivedAt.toDateString() === day.toDateString()) return 'ganttview-received'; - if (cardDates.startAt && cardDates.startAt.toDateString() === day.toDateString()) return 'ganttview-start'; - if (cardDates.dueAt && cardDates.dueAt.toDateString() === day.toDateString()) return 'ganttview-due'; - if (cardDates.endAt && cardDates.endAt.toDateString() === day.toDateString()) return 'ganttview-end'; - return ''; - }, - cellContent(card, day) { - const cardDates = { - receivedAt: card.receivedAt ? new Date(card.receivedAt) : null, - startAt: card.startAt ? new Date(card.startAt) : null, - dueAt: card.dueAt ? new Date(card.dueAt) : null, - endAt: card.endAt ? new Date(card.endAt) : null, - }; - if (cardDates.receivedAt && cardDates.receivedAt.toDateString() === day.toDateString()) return '📥'; - if (cardDates.startAt && cardDates.startAt.toDateString() === day.toDateString()) return '🚀'; - if (cardDates.dueAt && cardDates.dueAt.toDateString() === day.toDateString()) return '⏰'; - if (cardDates.endAt && cardDates.endAt.toDateString() === day.toDateString()) return '🏁'; - return ''; - }, - isToday(day) { - const today = new Date(); - return day.toDateString() === today.toDateString(); - }, - isWeekend(day) { - const idx = day.getDay(); - return idx === 0 || idx === 6; - }, - hasSelectedCard() { - return Template.instance().selectedCardId.get() !== null; - }, - selectedCard() { - const cardId = Template.instance().selectedCardId.get(); - return cardId ? ReactiveCache.getCard(cardId) : null; - }, - cellClasses(card, day) { - // Get the base class from cellContentClass logic - const cardDates = { - receivedAt: card.receivedAt ? new Date(card.receivedAt) : null, - startAt: card.startAt ? new Date(card.startAt) : null, - dueAt: card.dueAt ? new Date(card.dueAt) : null, - endAt: card.endAt ? new Date(card.endAt) : null, - }; - let classes = ''; - if (cardDates.receivedAt && cardDates.receivedAt.toDateString() === day.toDateString()) classes = 'ganttview-received'; - else if (cardDates.startAt && cardDates.startAt.toDateString() === day.toDateString()) classes = 'ganttview-start'; - else if (cardDates.dueAt && cardDates.dueAt.toDateString() === day.toDateString()) classes = 'ganttview-due'; - else if (cardDates.endAt && cardDates.endAt.toDateString() === day.toDateString()) classes = 'ganttview-end'; - - // Add conditional classes - const today = new Date(); - if (day.toDateString() === today.toDateString()) classes += ' ganttview-today'; - const idx = day.getDay(); - if (idx === 0 || idx === 6) classes += ' ganttview-weekend'; - if (classes.trim()) classes += ' js-gantt-date-icon'; - - return classes.trim(); - } -}); - -Template.ganttView.onCreated(function() { - this.selectedCardId = new ReactiveVar(null); - // Provide properties expected by cardDetails component - this.showOverlay = new ReactiveVar(false); - this.mouseHasEnterCardDetails = false; -}); - -// Blaze onRendered logic for ganttView -Template.ganttView.onRendered(function() { - const self = this; - this.autorun(() => { - // If you have legacy imperative rendering, keep it here - if (typeof renderGanttChart === 'function') { - renderGanttChart(); - } - }); - // Add click handler for date cells (Received, Start, Due, End) - this.$('.gantt-table').on('click', '.js-gantt-date-icon', function(e) { - e.preventDefault(); - e.stopPropagation(); - const $cell = self.$(this); - const cardId = $cell.data('card-id'); - let dateType = $cell.data('date-type'); - // Remove 'ganttview-' prefix to match popup map - if (typeof dateType === 'string' && dateType.startsWith('ganttview-')) { - dateType = dateType.replace('ganttview-', ''); - } - const popupMap = { - received: 'editCardReceivedDate', - start: 'editCardStartDate', - due: 'editCardDueDate', - end: 'editCardEndDate', - }; - const popupName = popupMap[dateType]; - if (!popupName || typeof Popup === 'undefined' || typeof Popup.open !== 'function') return; - const card = ReactiveCache.getCard(cardId); - if (!card) return; - const openFn = Popup.open(popupName); - openFn.call({ currentData: () => card }, e, { dataContextIfCurrentDataIsUndefined: card }); - }); - -}); - -import markdownit from 'markdown-it'; -import { TAPi18n } from '/imports/i18n'; -import { formatDateByUserPreference } from '/imports/lib/dateUtils'; -import { ReactiveCache } from '/imports/reactiveCache'; - -const md = markdownit({ breaks: true, linkify: true }); diff --git a/client/components/gantt/ganttCard.css b/client/components/gantt/ganttCard.css deleted file mode 100644 index d43c5ee93..000000000 --- a/client/components/gantt/ganttCard.css +++ /dev/null @@ -1,47 +0,0 @@ -.gantt-card-wrapper { - background: white; - border: 1px solid #ddd; - border-radius: 5px; - margin: 1rem 0; - padding: 1rem; - box-shadow: 0 2px 4px rgba(0,0,0,0.1); -} - -.gantt-card-header { - display: flex; - justify-content: space-between; - align-items: center; - margin-bottom: 1rem; - border-bottom: 1px solid #eee; - padding-bottom: 0.5rem; -} - -.gantt-card-header h3 { - margin: 0; - color: #333; -} - -.close-button { - background: none; - border: none; - font-size: 1.5rem; - cursor: pointer; - color: #666; - padding: 0; - width: 30px; - height: 30px; - border-radius: 50%; - display: flex; - align-items: center; - justify-content: center; -} - -.close-button:hover { - background-color: #f0f0f0; - color: #333; -} - -.gantt-card-content { - max-height: 400px; - overflow-y: auto; -} \ No newline at end of file diff --git a/client/components/gantt/ganttCard.jade b/client/components/gantt/ganttCard.jade deleted file mode 100644 index cbe2474ef..000000000 --- a/client/components/gantt/ganttCard.jade +++ /dev/null @@ -1,2 +0,0 @@ -template(name="ganttCard") - +cardDetails(selectedCard) \ No newline at end of file diff --git a/client/components/gantt/ganttCard.js b/client/components/gantt/ganttCard.js deleted file mode 100644 index 9198425ee..000000000 --- a/client/components/gantt/ganttCard.js +++ /dev/null @@ -1,41 +0,0 @@ -Template.ganttCard.onCreated(function () { - // Provide the expected parent component properties for cardDetails - this.showOverlay = new ReactiveVar(false); - this.mouseHasEnterCardDetails = false; -}); - -Template.ganttCard.helpers({ - selectedCard() { - // The selected card is now passed as a parameter to the component - return Template.currentData(); - }, -}); - -Template.ganttCard.events({ - 'click .js-close-card-details'(event) { - event.preventDefault(); - // Find the ganttView template instance and clear selectedCardId - let view = Blaze.currentView; - while (view) { - if (view.templateInstance && view.templateInstance().selectedCardId) { - view.templateInstance().selectedCardId.set(null); - break; - } - view = view.parentView; - } - }, -}); - -// Add click handler to ganttView for card titles -Template.ganttView.events({ - 'click .js-gantt-card-title'(event, template) { - event.preventDefault(); - // Get card ID from the closest row's data attribute - const $row = template.$(event.currentTarget).closest('tr'); - const cardId = $row.data('card-id'); - - if (cardId) { - template.selectedCardId.set(cardId); - } - }, -}); \ No newline at end of file diff --git a/client/components/import/import.jade b/client/components/import/import.jade index 3a85ce75a..ed42fe44b 100644 --- a/client/components/import/import.jade +++ b/client/components/import/import.jade @@ -1,7 +1,7 @@ template(name="importHeaderBar") h1 a.back-btn(href="{{pathFor 'home'}}") - i.fa-arrow-left + i.fa.fa-chevron-left | {{_ title}} template(name="import") diff --git a/client/components/import/import.js b/client/components/import/import.js index 5e708f4d0..757b55e41 100644 --- a/client/components/import/import.js +++ b/client/components/import/import.js @@ -1,69 +1,40 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { trelloGetMembersToMap } from './trelloMembersMapper'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import { wekanGetMembersToMap } from './wekanMembersMapper'; import { csvGetMembersToMap } from './csvMembersMapper'; -import getSlug from 'limax'; const Papa = require('papaparse'); -Template.importHeaderBar.helpers({ +BlazeComponent.extendComponent({ title() { return `import-board-title-${Session.get('importSource')}`; }, -}); +}).register('importHeaderBar'); -// Helper to find the closest ancestor template instance by name -function findParentTemplateInstance(childTemplateInstance, parentTemplateName) { - let view = childTemplateInstance.view; - while (view) { - if (view.name === `Template.${parentTemplateName}` && view.templateInstance) { - return view.templateInstance(); - } - view = view.parentView; - } - return null; -} +BlazeComponent.extendComponent({ + onCreated() { + this.error = new ReactiveVar(''); + this.steps = ['importTextarea', 'importMapMembers']; + this._currentStepIndex = new ReactiveVar(0); + this.importedData = new ReactiveVar(); + this.membersToMap = new ReactiveVar([]); + this.importSource = Session.get('importSource'); + }, -function _prepareAdditionalData(dataObject) { - const importSource = Session.get('importSource'); - let membersToMap; - switch (importSource) { - case 'trello': - membersToMap = trelloGetMembersToMap(dataObject); - break; - case 'wekan': - membersToMap = wekanGetMembersToMap(dataObject); - break; - case 'csv': - membersToMap = csvGetMembersToMap(dataObject); - break; - } - return membersToMap; -} + currentTemplate() { + return this.steps[this._currentStepIndex.get()]; + }, -Template.import.onCreated(function () { - this.error = new ReactiveVar(''); - this.steps = ['importTextarea', 'importMapMembers']; - this._currentStepIndex = new ReactiveVar(0); - this.importedData = new ReactiveVar(); - this.membersToMap = new ReactiveVar([]); - this.importSource = Session.get('importSource'); - - this.nextStep = () => { + nextStep() { const nextStepIndex = this._currentStepIndex.get() + 1; if (nextStepIndex >= this.steps.length) { this.finishImport(); } else { this._currentStepIndex.set(nextStepIndex); } - }; + }, - this.setError = (error) => { - this.error.set(error); - }; - - this.importData = (evt, dataSource) => { + importData(evt, dataSource) { evt.preventDefault(); const input = this.find('.js-import-json').value; if (dataSource === 'csv') { @@ -71,7 +42,7 @@ Template.import.onCreated(function () { const ret = Papa.parse(csv); if (ret && ret.data && ret.data.length) this.importedData.set(ret.data); else throw new Meteor.Error('error-csv-schema'); - const membersToMap = _prepareAdditionalData(ret.data); + const membersToMap = this._prepareAdditionalData(ret.data); this.membersToMap.set(membersToMap); this.nextStep(); } else { @@ -79,7 +50,7 @@ Template.import.onCreated(function () { const dataObject = JSON.parse(input); this.setError(''); this.importedData.set(dataObject); - const membersToMap = _prepareAdditionalData(dataObject); + const membersToMap = this._prepareAdditionalData(dataObject); // store members data and mapping in Session // (we go deep and 2-way, so storing in data context is not a viable option) this.membersToMap.set(membersToMap); @@ -88,9 +59,13 @@ Template.import.onCreated(function () { this.setError('error-json-malformed'); } } - }; + }, - this.finishImport = () => { + setError(error) { + this.error.set(error); + }, + + finishImport() { const additionalData = {}; const membersMapping = this.membersToMap.get(); if (membersMapping) { @@ -118,27 +93,44 @@ Template.import.onCreated(function () { FlowRouter.go('board', { id: res, slug: title, - }); + }) //Utils.goBoardId(res); } }, ); - }; -}); - -Template.import.helpers({ - error() { - return Template.instance().error; }, - currentTemplate() { - return Template.instance().steps[Template.instance()._currentStepIndex.get()]; - }, -}); -Template.importTextarea.helpers({ + _prepareAdditionalData(dataObject) { + const importSource = Session.get('importSource'); + let membersToMap; + switch (importSource) { + case 'trello': + membersToMap = trelloGetMembersToMap(dataObject); + break; + case 'wekan': + membersToMap = wekanGetMembersToMap(dataObject); + break; + case 'csv': + membersToMap = csvGetMembersToMap(dataObject); + break; + } + return membersToMap; + }, + + _screenAdditionalData() { + return 'mapMembers'; + }, +}).register('import'); + +BlazeComponent.extendComponent({ + template() { + return 'importTextarea'; + }, + instruction() { return `import-board-instruction-${Session.get('importSource')}`; }, + importPlaceHolder() { const importSource = Session.get('importSource'); if (importSource === 'csv') { @@ -147,37 +139,81 @@ Template.importTextarea.helpers({ return 'import-json-placeholder'; } }, -}); -Template.importTextarea.events({ - submit(evt, tpl) { - const importTpl = findParentTemplateInstance(tpl, 'import'); - if (importTpl) { - return importTpl.importData(evt, Session.get('importSource')); - } + events() { + return [ + { + submit(evt) { + return this.parentComponent().importData( + evt, + Session.get('importSource'), + ); + }, + }, + ]; }, -}); +}).register('importTextarea'); -// Module-level reference so popup children can access importMapMembers methods -let _importMapMembersTpl = null; +BlazeComponent.extendComponent({ + onCreated() { + this.usersLoaded = new ReactiveVar(false); -Template.importMapMembers.onCreated(function () { - _importMapMembersTpl = this; - this.usersLoaded = new ReactiveVar(false); + this.autorun(() => { + const handle = this.subscribe( + 'user-miniprofile', + this.members().map(member => { + return member.username; + }), + ); + Tracker.nonreactive(() => { + Tracker.autorun(() => { + if ( + handle.ready() && + !this.usersLoaded.get() && + this.members().length + ) { + this._refreshMembers( + this.members().map(member => { + if (!member.wekanId) { + let user = ReactiveCache.getUser({ username: member.username }); + if (!user) { + user = ReactiveCache.getUser({ importUsernames: member.username }); + } + if (user) { + // eslint-disable-next-line no-console + // console.log('found username:', user.username); + member.wekanId = user._id; + } + } + return member; + }), + ); + } + this.usersLoaded.set(handle.ready()); + }); + }); + }); + }, - this.members = () => { - const importTpl = findParentTemplateInstance(this, 'import'); - return importTpl ? importTpl.membersToMap.get() : []; - }; + members() { + return this.parentComponent().membersToMap.get(); + }, - this._refreshMembers = (listOfMembers) => { - const importTpl = findParentTemplateInstance(this, 'import'); - if (importTpl) { - importTpl.membersToMap.set(listOfMembers); - } - }; + _refreshMembers(listOfMembers) { + return this.parentComponent().membersToMap.set(listOfMembers); + }, - this._setPropertyForMember = (property, value, memberId, unset = false) => { + /** + * Will look into the list of members to import for the specified memberId, + * then set its property to the supplied value. + * If unset is true, it will remove the property from the rest of the list as well. + * + * use: + * - memberId = null to use selected member + * - value = null to unset a property + * - unset = true to ensure property is only set on 1 member at a time + */ + _setPropertyForMember(property, value, memberId, unset = false) { const listOfMembers = this.members(); let finder = null; if (memberId) { @@ -203,13 +239,17 @@ Template.importMapMembers.onCreated(function () { }); // Session.get gives us a copy, we have to set it back so it sticks this._refreshMembers(listOfMembers); - }; + }, - this.setSelectedMember = (memberId) => { + setSelectedMember(memberId) { return this._setPropertyForMember('selected', true, memberId, true); - }; + }, - this.getMember = (memberId = null) => { + /** + * returns the member with specified id, + * or the selected member if memberId is not specified + */ + getMember(memberId = null) { const allMembers = this.members(); let finder = null; if (memberId) { @@ -218,154 +258,117 @@ Template.importMapMembers.onCreated(function () { finder = user => user.selected; } return allMembers.find(finder); - }; + }, - this.mapSelectedMember = (wekanId) => { + mapSelectedMember(wekanId) { return this._setPropertyForMember('wekanId', wekanId, null); - }; + }, - this.unmapMember = (memberId) => { + unmapMember(memberId) { return this._setPropertyForMember('wekanId', null, memberId); - }; - - this.autorun(() => { - const handle = this.subscribe( - 'user-miniprofile', - this.members().map(member => { - return member.username; - }), - ); - Tracker.nonreactive(() => { - Tracker.autorun(() => { - if ( - handle.ready() && - !this.usersLoaded.get() && - this.members().length - ) { - this._refreshMembers( - this.members().map(member => { - if (!member.wekanId) { - let user = ReactiveCache.getUser({ username: member.username }); - if (!user) { - user = ReactiveCache.getUser({ importUsernames: member.username }); - } - if (user) { - member.wekanId = user._id; - } - } - return member; - }), - ); - } - this.usersLoaded.set(handle.ready()); - }); - }); - }); -}); - -Template.importMapMembers.onDestroyed(function () { - if (_importMapMembersTpl === this) { - _importMapMembersTpl = null; - } -}); - -Template.importMapMembers.helpers({ - usersLoaded() { - return Template.instance().usersLoaded; }, - members() { - return Template.instance().members(); - }, -}); -Template.importMapMembers.events({ - submit(evt, tpl) { + onSubmit(evt) { evt.preventDefault(); - const importTpl = findParentTemplateInstance(tpl, 'import'); - if (importTpl) { - importTpl.nextStep(); - } + this.parentComponent().nextStep(); }, - 'click .js-select-member'(evt, tpl) { - const memberToMap = Template.currentData(); - if (memberToMap.wekan) { - // todo xxx ask for confirmation? - tpl.unmapMember(memberToMap.id); - } else { - tpl.setSelectedMember(memberToMap.id); - Popup.open('importMapMembersAdd')(evt); - } + + events() { + return [ + { + submit: this.onSubmit, + 'click .js-select-member'(evt) { + const memberToMap = this.currentData(); + if (memberToMap.wekan) { + // todo xxx ask for confirmation? + this.unmapMember(memberToMap.id); + } else { + this.setSelectedMember(memberToMap.id); + Popup.open('importMapMembersAdd')(evt); + } + }, + }, + ]; }, -}); +}).register('importMapMembers'); + +BlazeComponent.extendComponent({ + onRendered() { + this.find('.js-map-member input').focus(); + }, + + onSelectUser() { + Popup.getOpenerComponent(5).mapSelectedMember(this.currentData().__originalId); + Popup.back(); + }, + + events() { + return [ + { + 'click .js-select-import': this.onSelectUser, + }, + ]; + }, +}).register('importMapMembersAddPopup'); // Global reactive variables for import member popup const importMemberPopupState = { searching: new ReactiveVar(false), searchResults: new ReactiveVar([]), noResults: new ReactiveVar(false), - searchTimeout: null, + searchTimeout: null }; -Template.importMapMembersAddPopup.onCreated(function () { - this.searching = importMemberPopupState.searching; - this.searchResults = importMemberPopupState.searchResults; - this.noResults = importMemberPopupState.noResults; - this.searchTimeout = null; - - this.searching.set(false); - this.searchResults.set([]); - this.noResults.set(false); -}); - -Template.importMapMembersAddPopup.onRendered(function () { - this.find('.js-search-member-input').focus(); -}); - -Template.importMapMembersAddPopup.onDestroyed(function () { - if (this.searchTimeout) { - clearTimeout(this.searchTimeout); - } - this.searching.set(false); -}); - -function importPerformSearch(tpl, query) { - if (!query || query.length < 2) { - tpl.searchResults.set([]); - tpl.noResults.set(false); - return; - } - - tpl.searching.set(true); - tpl.noResults.set(false); - - const results = UserSearchIndex.search(query, { limit: 20 }).fetch(); - tpl.searchResults.set(results); - tpl.searching.set(false); - - if (results.length === 0) { - tpl.noResults.set(true); - } -} - -Template.importMapMembersAddPopup.events({ - 'click .js-select-import'(event, tpl) { - if (_importMapMembersTpl) { - _importMapMembersTpl.mapSelectedMember(Template.currentData().__originalId); - } - Popup.back(); +BlazeComponent.extendComponent({ + onCreated() { + // Use global state + this.searching = importMemberPopupState.searching; + this.searchResults = importMemberPopupState.searchResults; + this.noResults = importMemberPopupState.noResults; + this.searchTimeout = importMemberPopupState.searchTimeout; }, - 'keyup .js-search-member-input'(event, tpl) { - const query = event.target.value.trim(); - if (tpl.searchTimeout) { - clearTimeout(tpl.searchTimeout); + onRendered() { + this.find('.js-search-member-input').focus(); + }, + + performSearch(query) { + if (!query || query.length < 2) { + this.searchResults.set([]); + this.noResults.set(false); + return; } - tpl.searchTimeout = setTimeout(() => { - importPerformSearch(tpl, query); - }, 300); + this.searching.set(true); + this.noResults.set(false); + + const results = UserSearchIndex.search(query, { limit: 20 }).fetch(); + this.searchResults.set(results); + this.searching.set(false); + + if (results.length === 0) { + this.noResults.set(true); + } }, -}); + + events() { + return [ + { + 'keyup .js-search-member-input'(event) { + const query = event.target.value.trim(); + + if (this.searchTimeout) { + clearTimeout(this.searchTimeout); + } + + this.searchTimeout = setTimeout(() => { + this.performSearch(query); + }, 300); + }, + }, + ]; + }, +}).register('importMapMembersAddPopupSearch'); Template.importMapMembersAddPopup.helpers({ searchResults() { @@ -376,5 +379,5 @@ Template.importMapMembersAddPopup.helpers({ }, noResults() { return importMemberPopupState.noResults; - }, -}); + } +}) diff --git a/client/components/lib/basicTabs.jade b/client/components/lib/basicTabs.jade deleted file mode 100644 index cb08e81a6..000000000 --- a/client/components/lib/basicTabs.jade +++ /dev/null @@ -1,11 +0,0 @@ -template(name="basicTabs") - .basicTabs-container(class="{{name}}") - ul.tabs-list - each tabs - li.tab-item(class="{{isActiveTab slug}} {{class}}") {{name}} - .tabs-content-container - +Template.contentBlock - -template(name="tabContent") - section.tabs-content(class="{{isActiveTab slug}}" data-tab="{{slug}}") - +Template.contentBlock diff --git a/client/components/lib/basicTabs.js b/client/components/lib/basicTabs.js deleted file mode 100644 index 65892a2fa..000000000 --- a/client/components/lib/basicTabs.js +++ /dev/null @@ -1,50 +0,0 @@ -const { ReactiveVar } = require('meteor/reactive-var'); - -Template.basicTabs.onCreated(function () { - const activeTab = this.data.activeTab - ? { slug: this.data.activeTab } - : this.data.tabs[0]; - this._activeTab = new ReactiveVar(activeTab); - - this.isActiveSlug = (slug) => { - const current = this._activeTab.get(); - return current && current.slug === slug; - }; -}); - -Template.basicTabs.helpers({ - isActiveTab(slug) { - if (Template.instance().isActiveSlug(slug)) { - return 'active'; - } - }, -}); - -Template.basicTabs.events({ - 'click .tab-item'(e, t) { - t._activeTab.set(this); - }, -}); - -function findBasicTabsInstance() { - let view = Blaze.currentView; - while (view) { - if (view.name === 'Template.basicTabs' && view.templateInstance) { - const inst = view.templateInstance(); - if (inst && inst.isActiveSlug) { - return inst; - } - } - view = view.parentView; - } - return null; -} - -Template.tabContent.helpers({ - isActiveTab(slug) { - const inst = findBasicTabsInstance(); - if (inst && inst.isActiveSlug(slug)) { - return 'active'; - } - }, -}); diff --git a/client/components/lists/list.css b/client/components/lists/list.css index 1cfd6ca6f..77e78de29 100644 --- a/client/components/lists/list.css +++ b/client/components/lists/list.css @@ -161,66 +161,74 @@ body.list-resizing-active * { /* Use original display for consistent button positioning */ display: block !important; position: relative !important; - /* Allow overflow for text wrapping and forms */ - overflow: visible !important; -} - -/* Clearfix for floated buttons */ -.list-header::after { - content: ""; - display: table; - clear: both; + /* Prevent vertical expansion but allow normal height */ + overflow: hidden !important; } /* Ensure title text doesn't cause height changes for all lists */ .list-header .list-header-name { - /* Allow text wrapping to flow below buttons */ - white-space: normal !important; + /* Prevent text wrapping to maintain consistent height */ + white-space: nowrap !important; + /* Truncate text with ellipsis if too long */ + text-overflow: ellipsis !important; /* Ensure proper line height */ line-height: 1.2 !important; - /* Ensure it doesn't overflow horizontally */ - overflow-wrap: break-word !important; - word-wrap: break-word !important; - /* Full width since buttons are now absolutely positioned above */ - width: 100% !important; + /* Ensure it doesn't overflow */ + overflow: hidden !important; + /* Add margin to prevent overlap with buttons */ + margin-right: 120px !important; } -/* Position elements at top aligned with collapse button */ -.list-header .js-open-list-menu { +/* Position drag handle at top-right corner for ALL lists */ +.list-header .list-header-handle { + /* Position at top-right corner, aligned with title text top */ position: absolute !important; - top: 5px !important; - right: 10px !important; - z-index: 15 !important; - display: inline-block !important; - padding: 4px !important; -} - -.list-header .list-header-plus-top { - position: absolute !important; - top: 5px !important; - right: 30px !important; - z-index: 15 !important; - display: inline-block !important; - padding: 4px !important; -} - -.list-header .list-header-handle-desktop { - position: absolute !important; - top: 5px !important; - right: 80px !important; + top: 2.5vh !important; + right: 1.5vw !important; + /* Ensure it's above other elements */ z-index: 15 !important; + /* Remove margin since it's absolutely positioned */ + margin-right: 0 !important; + /* Ensure proper display */ display: inline-block !important; + /* Ensure it's clickable and shows proper cursor */ cursor: move !important; pointer-events: auto !important; + /* Add some padding for better clickability */ padding: 4px !important; } -/* Anchor header action buttons within header during resize */ -.list .list-header { position: relative; z-index: 5; } -.list .list-header .js-open-list-menu, -.list .list-header .list-header-plus-top, -.list .list-header .list-header-handle-desktop { - position: absolute !important; +/* Ensure buttons maintain original positioning */ +.js-swimlane .list[style*="--list-width"] .list-header .list-header-plus-top, +.js-swimlane .list[style*="--list-width"] .list-header .js-collapse, +.js-swimlane .list[style*="--list-width"] .list-header .js-open-list-menu, +.dragscroll .list[style*="--list-width"] .list-header .list-header-plus-top, +.dragscroll .list[style*="--list-width"] .list-header .js-collapse, +.dragscroll .list[style*="--list-width"] .list-header .js-open-list-menu, +[id^="swimlane-"] .list[style*="--list-width"] .list-header .list-header-plus-top, +[id^="swimlane-"] .list[style*="--list-width"] .list-header .js-collapse, +[id^="swimlane-"] .list[style*="--list-width"] .list-header .js-open-list-menu { + /* Use original positioning to maintain layout */ + position: relative !important; + /* Maintain original spacing */ + margin-right: 15px !important; + /* Ensure proper display */ + display: inline-block !important; +} + +/* Ensure watch icon and card count maintain original positioning */ +.js-swimlane .list[style*="--list-width"] .list-header .list-header-watch-icon, +.dragscroll .list[style*="--list-width"] .list-header .list-header-watch-icon, +[id^="swimlane-"] .list[style*="--list-width"] .list-header .list-header-watch-icon, +.js-swimlane .list[style*="--list-width"] .list-header .cardCount, +.dragscroll .list[style*="--list-width"] .list-header .cardCount, +[id^="swimlane-"] .list[style*="--list-width"] .list-header .cardCount { + /* Use original positioning to maintain layout */ + position: relative !important; + /* Maintain original spacing */ + margin-right: 15px !important; + /* Ensure proper display */ + display: inline-block !important; } [id^="swimlane-"] .list:first-child { min-width: 2.5vw; @@ -251,61 +259,36 @@ body.list-resizing-active * { } .list.list-collapsed { flex: none; - min-width: 30px; - max-width: 30px; - width: 30px; + min-width: 60px; + max-width: 80px; + width: 60px; min-height: 60vh; height: 60vh; overflow: visible; position: relative; } .list.list-collapsed .list-header { - padding: 5px 0; - min-height: 100% !important; - height: 100% !important; + padding: 1vh 1.5vw 0.5vh; + min-height: 2.5vh !important; + height: auto !important; display: flex; flex-direction: column; align-items: center; justify-content: flex-start; position: relative; overflow: visible !important; - width: 30px; - max-width: 30px; - margin: 0; + width: 100%; + max-width: 60px; + margin: 0 auto; } .list.list-collapsed .list-header .js-collapse { - position: relative !important; - left: -10px !important; - margin: 5px auto; + margin: 0 auto 20px auto; z-index: 10; - padding: 5px; - font-size: 16px; + padding: 8px 12px; + font-size: 12px; white-space: nowrap; display: block; - width: auto; - left: auto !important; - top: auto !important; -} -.list.list-collapsed .list-header .list-header-handle { - position: static !important; - margin: 5px auto; - z-index: 10; - padding: 5px; - display: block; - width: auto; - top: auto !important; - right: auto !important; -} - -.list.list-collapsed .list-header .list-header-handle-desktop { - position: static !important; - margin: 5px auto; - z-index: 10; - padding: 5px; - display: block; - width: auto; - top: auto !important; - right: auto !important; + width: fit-content; } .list.list-collapsed .list-header .list-rotated { width: auto !important; @@ -313,43 +296,31 @@ body.list-resizing-active * { margin: 20px 0 0 0 !important; position: relative !important; overflow: visible !important; - transform: rotate(90deg); - transform-origin: center center; - flex: 1; - display: flex; - align-items: center; - justify-content: center; } + .list.list-collapsed .list-header .list-rotated h2.list-header-name { - text-align: center; + text-align: left; overflow: visible; white-space: nowrap; display: block !important; font-size: 12px; line-height: 1.2; color: #333; - padding: 4px 8px; - margin: 0; - width: auto; - height: auto; - position: static; - left: auto; - top: auto; - transform: none; + background-color: rgba(255, 255, 255, 0.95); + border: 1px solid #ddd; + padding: 8px 4px; + border-radius: 4px; + margin: 0 auto; + width: 25vh; + height: 60vh; + position: absolute; + left: 50%; + top: 50%; + transform: translate(calc(-50% + 50px), -50%) rotate(0deg); z-index: 10; visibility: visible !important; opacity: 1 !important; - pointer-events: auto; -} - -.list.list-composer, -.list-composer { - display: none; -} - -/* Show list-composer when inside an active inlined form */ -form.inlined-form .list-composer { - display: block; + pointer-events: none; } .list.list-composer .open-list-composer, @@ -386,29 +357,16 @@ form.inlined-form .list-composer { display: none; } .list-header .list-header-name { - display: block; + display: inline; font-size: clamp(14px, 3vw, 18px); line-height: 1.2; margin: 0; font-weight: bold; min-height: 1.2vh; min-width: 4vw; - overflow-wrap: break-word; + overflow: hidden; + text-overflow: ellipsis; word-wrap: break-word; - vertical-align: top; - width: 100%; -} -/* Sum badge shown before list title */ -.list-header .list-sum-badge { - display: inline-block; - margin-right: 8px; - padding: 0; - border-radius: 0; - background: transparent; - color: #8c8c8c; - font-weight: bold; - font-size: 12px; - vertical-align: middle; } .list-rotated { width: 1.3vw; @@ -437,8 +395,6 @@ form.inlined-form .list-composer { .list-header .list-header-plus-top { color: #a6a6a6; margin-right: 15px; - vertical-align: middle; - line-height: 1.2; } .list-header .list-header-collapse-right { color: #a6a6a6; @@ -447,194 +403,151 @@ form.inlined-form .list-composer { color: #a6a6a6; margin-right: 15px; } -/* List header collapse button styling - positioned at top left */ .list-header .js-collapse { - position: absolute !important; - top: 5px !important; - left: 10px !important; color: #a6a6a6; + margin-right: 15px; display: inline-block; - vertical-align: top; + vertical-align: middle; padding: 5px 8px; - border: none; - border-radius: 0; - background-color: transparent; + border: 1px solid #ccc; + border-radius: 4px; + background-color: #f5f5f5; cursor: pointer; - font-size: 18px; - line-height: 1.2; - min-width: 30px; - text-align: center; - text-decoration: none; - margin: 0; - z-index: 15; + font-size: 14px; } .list-header .js-collapse:hover { - background-color: transparent; + background-color: #e0e0e0; color: #333; } - -/* Title text container - full width below buttons */ -.list-header > div { - padding-top: 25px; - width: 100%; - display: block; - clear: both; -} .list.list-collapsed .list-header .js-collapse { display: inline-block !important; visibility: visible !important; opacity: 1 !important; } -/* Hide menu button in collapsed state */ -.list.list-collapsed .list-header .js-open-list-menu, -.list.list-collapsed .list-header .list-header-menu { - display: none !important; -} - /* Responsive adjustments for collapsed lists */ @media (min-width: 768px) { .list.list-collapsed { - min-width: 30px; - max-width: 30px; - width: 30px; + min-width: 60px; + max-width: 80px; + width: 60px; min-height: 60vh; height: 60vh; } .list.list-collapsed .list-header { - width: 30px; - max-width: 30px; - margin: 0; - min-height: 100% !important; - height: 100% !important; + max-width: 60px; + margin: 0 auto; + min-height: 2.5vh !important; + height: auto !important; } .list.list-collapsed .list-header .list-rotated { width: auto !important; height: auto !important; margin: 20px 0 0 0 !important; position: relative !important; - transform: rotate(90deg); - flex: 1; } .list.list-collapsed .list-header .list-rotated h2.list-header-name { - width: auto; + width: 15vh; font-size: 12px; - height: auto; + height: 30px; line-height: 1.2; - padding: 4px 8px; - margin: 0; - overflow: visible; - position: static; - left: auto; - top: auto; - transform: none; - text-align: center; + padding: 8px 4px; + margin: 0 auto; + position: absolute; + left: 50%; + top: 50%; + transform: translate(calc(-50% + 50px), -50%) rotate(0deg); + text-align: left; visibility: visible !important; opacity: 1 !important; display: block !important; - background-color: transparent; - border: none; + background-color: rgba(255, 255, 255, 0.95); + border: 1px solid #ddd; color: #333; z-index: 10; } .list.list-collapsed .list-header .js-collapse { - margin: 5px auto; + margin: 0 auto 20px auto; } } @media (min-width: 1024px) { .list.list-collapsed { - min-width: 30px; - max-width: 30px; - width: 30px; min-height: 60vh; height: 60vh; } .list.list-collapsed .list-header { - width: 30px; - max-width: 30px; - min-height: 100% !important; - height: 100% !important; + min-height: 2.5vh !important; + height: auto !important; } .list.list-collapsed .list-header .list-rotated { width: auto !important; height: auto !important; margin: 20px 0 0 0 !important; position: relative !important; - transform: rotate(90deg); - flex: 1; } .list.list-collapsed .list-header .list-rotated h2.list-header-name { - width: auto; + width: 15vh; font-size: 12px; - height: auto; + height: 30px; line-height: 1.2; - padding: 4px 8px; - margin: 0; - overflow: visible; - position: static; - left: auto; - top: auto; - transform: none; - text-align: center; + padding: 8px 4px; + margin: 0 auto; + position: absolute; + left: 50%; + top: 50%; + transform: translate(calc(-50% + 50px), -50%) rotate(0deg); + text-align: left; visibility: visible !important; opacity: 1 !important; display: block !important; - background-color: transparent; - border: none; + background-color: rgba(255, 255, 255, 0.95); + border: 1px solid #ddd; color: #333; z-index: 10; } .list.list-collapsed .list-header .js-collapse { - margin: 5px auto; + margin: 0 auto 20px auto; } } @media (min-width: 1200px) { .list.list-collapsed { - min-width: 30px; - max-width: 30px; - width: 30px; min-height: 60vh; height: 60vh; } .list.list-collapsed .list-header { - width: 30px; - max-width: 30px; - min-height: 100% !important; - height: 100% !important; + min-height: 2.5vh !important; + height: auto !important; } .list.list-collapsed .list-header .list-rotated { width: auto !important; height: auto !important; margin: 20px 0 0 0 !important; position: relative !important; - transform: rotate(90deg); - flex: 1; } .list.list-collapsed .list-header .list-rotated h2.list-header-name { - width: auto; + width: 15vh; font-size: 12px; - height: auto; + height: 30px; line-height: 1.2; - padding: 4px 8px; - margin: 0; - overflow: visible; - position: static; - left: auto; - top: auto; - transform: none; - text-align: center; + padding: 8px 4px; + margin: 0 auto; + position: absolute; + left: 50%; + top: 50%; + transform: translate(calc(-50% + 50px), -50%) rotate(0deg); + text-align: left; visibility: visible !important; opacity: 1 !important; display: block !important; - background-color: transparent; - border: none; + background-color: rgba(255, 255, 255, 0.95); + border: 1px solid #ddd; color: #333; z-index: 10; } .list.list-collapsed .list-header .js-collapse { - margin: 5px auto; + margin: 0 auto 20px auto; } } .list-header .list-header-collapse { @@ -657,8 +570,6 @@ form.inlined-form .list-composer { } .js-open-list-menu { font-size: 18px; - vertical-align: middle; - line-height: 1.2; } .list-body { flex: 1 1 auto; @@ -839,9 +750,6 @@ form.inlined-form .list-composer { grid-row: 2; grid-column: 2; align-self: start; - text-align: left; - padding-left: 0; - margin-left: 0; font-size: 16px !important; line-height: 1.2; } @@ -1056,9 +964,6 @@ form.inlined-form .list-composer { grid-row: 2 !important; grid-column: 2 !important; align-self: start !important; - text-align: left !important; - padding-left: 0 !important; - margin-left: 0 !important; font-size: 16px !important; line-height: 1.2 !important; } @@ -1130,23 +1035,6 @@ form.inlined-form .list-composer { grid-row: 1/3 !important; grid-column: 1 !important; } - -/* Allow long list titles to expand on desktop (non-mobile, non-collapsed) */ -.list:not(.mobile-view):not(.list-collapsed) .list-header { - overflow: visible !important; -} - -.list:not(.mobile-view):not(.list-collapsed) .list-header .list-header-name { - /* Permit wrapping and full visibility */ - white-space: normal !important; - overflow: visible !important; - text-overflow: clip !important; - display: block !important; - /* Full width since buttons are absolutely positioned */ - width: 100% !important; - /* Break long words to avoid overflow */ - word-break: break-word !important; -} .link-board-wrapper { display: flex; align-items: baseline; @@ -1236,48 +1124,3 @@ form.inlined-form .list-composer { .list-header-indigo { border-bottom: 6px solid #4b0082; } - -.list.list-collapsed .collapsed-list-drag-area { - width: 100%; - height: 60px; - display: flex; - align-items: center; - justify-content: center; - cursor: grab; - user-select: none; -} -.list.list-collapsed .collapsed-list-drag-area:active { - cursor: grabbing; -} -.list.list-collapsed .list-header-name-collapsed { - writing-mode: vertical-rl; - text-align: center; - font-size: 12px; - color: #333; - margin: 0; - padding: 0; - width: 100%; - white-space: nowrap; - overflow: hidden; - text-overflow: ellipsis; -} - -.list.list-collapsed .list-header .js-collapse { - position: relative !important; - left: -10px !important; - color: #333; - background: transparent; - border: none; - border-radius: 0; - width: auto; - height: auto; - min-width: 0; - min-height: 0; - display: block !important; - align-items: initial; - justify-content: initial; - font-size: 16px !important; - box-shadow: none; - margin: 5px auto; - z-index: 10; -} diff --git a/client/components/lists/list.jade b/client/components/lists/list.jade index c28dd1a9c..eed4d67f9 100644 --- a/client/components/lists/list.jade +++ b/client/components/lists/list.jade @@ -3,9 +3,8 @@ template(name='list') style="{{#unless collapsed}}min-width:{{listWidth}}px;max-width:{{listConstraint}}px;{{/unless}}" class="{{#if collapsed}}list-collapsed{{/if}} {{#if autoWidth}}list-auto-width{{/if}} {{#if isMiniScreen}}mobile-view{{/if}}") +listHeader - unless collapsed - +listBody - .list-resize-handle.js-list-resize-handle.nodragscroll + +listBody + .list-resize-handle.js-list-resize-handle.nodragscroll template(name='miniList') a.mini-list.js-select-list.js-list(id="js-list-{{_id}}" class="{{#if isMiniScreen}}mobile-view{{/if}}") diff --git a/client/components/lists/list.js b/client/components/lists/list.js index 76fb061da..7501886ae 100644 --- a/client/components/lists/list.js +++ b/client/components/lists/list.js @@ -4,207 +4,201 @@ require('/client/lib/jquery-ui.js') const { calculateIndex } = Utils; -Template.list.onCreated(function () { - this.newCardFormIsVisible = new ReactiveVar(true); +BlazeComponent.extendComponent({ + // Proxy + openForm(options) { + this.childComponents('listBody')[0].openForm(options); + }, - // Proxy - find the listBody child template instance via the DOM - this.openForm = (options) => { - const listBodyEl = this.find('.list-body'); - const view = listBodyEl && Blaze.getView(listBodyEl, 'Template.listBody'); - const listBodyInstance = view?.templateInstance?.(); - if (listBodyInstance) listBodyInstance.openForm(options); - }; -}); + onCreated() { + this.newCardFormIsVisible = new ReactiveVar(true); + }, -// The jquery UI sortable library is the best solution I've found so far. I -// tried sortable and dragula but they were not powerful enough four our use -// case. I also considered writing/forking a drag-and-drop + sortable library -// but it's probably too much work. -// By calling asking the sortable library to cancel its move on the `stop` -// callback, we basically solve all issues related to reactive updates. A -// comment below provides further details. -Template.list.onRendered(function () { - const boardBodyEl = this.firstNode?.parentElement?.closest?.('.board-body') || - document.querySelector('.board-body'); - const boardView = boardBodyEl && Blaze.getView(boardBodyEl, 'Template.boardBody'); - const boardComponent = boardView?.templateInstance?.(); + // The jquery UI sortable library is the best solution I've found so far. I + // tried sortable and dragula but they were not powerful enough four our use + // case. I also considered writing/forking a drag-and-drop + sortable library + // but it's probably too much work. + // By calling asking the sortable library to cancel its move on the `stop` + // callback, we basically solve all issues related to reactive updates. A + // comment below provides further details. + onRendered() { + const boardComponent = this.parentComponent().parentComponent(); - // Initialize list resize functionality immediately - this.initializeListResize(); + // Initialize list resize functionality immediately + this.initializeListResize(); - const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)'; - const $cards = this.$('.js-minicards'); + const itemsSelector = '.js-minicard:not(.placeholder, .js-card-composer)'; + const $cards = this.$('.js-minicards'); - $cards.sortable({ - connectWith: '.js-minicards:not(.js-list-full)', - tolerance: 'pointer', - appendTo: '.board-canvas', - helper(evt, item) { - const helper = item.clone(); - if (MultiSelection.isActive()) { - const andNOthers = $cards.find('.js-minicard.is-checked').length - 1; - if (andNOthers > 0) { - helper.append( - $( - Blaze.toHTML( - HTML.DIV( - { class: 'and-n-other' }, - TAPi18n.__('and-n-other-card', { count: andNOthers }), + $cards.sortable({ + connectWith: '.js-minicards:not(.js-list-full)', + tolerance: 'pointer', + appendTo: '.board-canvas', + helper(evt, item) { + const helper = item.clone(); + if (MultiSelection.isActive()) { + const andNOthers = $cards.find('.js-minicard.is-checked').length - 1; + if (andNOthers > 0) { + helper.append( + $( + Blaze.toHTML( + HTML.DIV( + { class: 'and-n-other' }, + TAPi18n.__('and-n-other-card', { count: andNOthers }), + ), ), ), - ), - ); + ); + } } - } - return helper; - }, - distance: 7, - items: itemsSelector, - placeholder: 'minicard-wrapper placeholder', - scrollSpeed: 10, - start(evt, ui) { - ui.helper.css('z-index', 1000); - ui.placeholder.height(ui.helper.height()); - EscapeActions.executeUpTo('popup-close'); - if (boardComponent) boardComponent.setIsDragging(true); - }, - stop(evt, ui) { - // To attribute the new index number, we need to get the DOM element - // of the previous and the following card -- if any. - const prevCardDom = ui.item.prev('.js-minicard').get(0); - const nextCardDom = ui.item.next('.js-minicard').get(0); - const nCards = MultiSelection.isActive() ? MultiSelection.count() : 1; - const sortIndex = calculateIndex(prevCardDom, nextCardDom, nCards); - const listId = Blaze.getData(ui.item.parents('.list').get(0))._id; - const currentBoard = Utils.getCurrentBoard(); - const defaultSwimlaneId = currentBoard.getDefaultSwimline()._id; - let targetSwimlaneId = null; + return helper; + }, + distance: 7, + items: itemsSelector, + placeholder: 'minicard-wrapper placeholder', + scrollSpeed: 10, + start(evt, ui) { + ui.helper.css('z-index', 1000); + ui.placeholder.height(ui.helper.height()); + EscapeActions.executeUpTo('popup-close'); + boardComponent.setIsDragging(true); + }, + stop(evt, ui) { + // To attribute the new index number, we need to get the DOM element + // of the previous and the following card -- if any. + const prevCardDom = ui.item.prev('.js-minicard').get(0); + const nextCardDom = ui.item.next('.js-minicard').get(0); + const nCards = MultiSelection.isActive() ? MultiSelection.count() : 1; + const sortIndex = calculateIndex(prevCardDom, nextCardDom, nCards); + const listId = Blaze.getData(ui.item.parents('.list').get(0))._id; + const currentBoard = Utils.getCurrentBoard(); + const defaultSwimlaneId = currentBoard.getDefaultSwimline()._id; + let targetSwimlaneId = null; - // only set a new swimelane ID if the swimlanes view is active - if ( - Utils.boardView() === 'board-view-swimlanes' || - currentBoard.isTemplatesBoard() - ) - targetSwimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0)) - ._id; + // only set a new swimelane ID if the swimlanes view is active + if ( + Utils.boardView() === 'board-view-swimlanes' || + currentBoard.isTemplatesBoard() + ) + targetSwimlaneId = Blaze.getData(ui.item.parents('.swimlane').get(0)) + ._id; - // Normally the jquery-ui sortable library moves the dragged DOM element - // to its new position, which disrupts Blaze reactive updates mechanism - // (especially when we move the last card of a list, or when multiple - // users move some cards at the same time). To prevent these UX glitches - // we ask sortable to gracefully cancel the move, and to put back the - // DOM in its initial state. The card move is then handled reactively by - // Blaze with the below query. - $cards.sortable('cancel'); + // Normally the jquery-ui sortable library moves the dragged DOM element + // to its new position, which disrupts Blaze reactive updates mechanism + // (especially when we move the last card of a list, or when multiple + // users move some cards at the same time). To prevent these UX glitches + // we ask sortable to gracefully cancel the move, and to put back the + // DOM in its initial state. The card move is then handled reactively by + // Blaze with the below query. + $cards.sortable('cancel'); - if (MultiSelection.isActive()) { - ReactiveCache.getCards(MultiSelection.getMongoSelector(), { sort: ['sort'] }).forEach((card, i) => { + if (MultiSelection.isActive()) { + ReactiveCache.getCards(MultiSelection.getMongoSelector(), { sort: ['sort'] }).forEach((card, i) => { + const newSwimlaneId = targetSwimlaneId + ? targetSwimlaneId + : card.swimlaneId || defaultSwimlaneId; + card.move( + currentBoard._id, + newSwimlaneId, + listId, + sortIndex.base + i * sortIndex.increment, + ); + }); + } else { + const cardDomElement = ui.item.get(0); + const card = Blaze.getData(cardDomElement); const newSwimlaneId = targetSwimlaneId ? targetSwimlaneId : card.swimlaneId || defaultSwimlaneId; - card.move( - currentBoard._id, - newSwimlaneId, - listId, - sortIndex.base + i * sortIndex.increment, - ); - }); - } else { - const cardDomElement = ui.item.get(0); - const card = Blaze.getData(cardDomElement); - const newSwimlaneId = targetSwimlaneId - ? targetSwimlaneId - : card.swimlaneId || defaultSwimlaneId; - card.move(currentBoard._id, newSwimlaneId, listId, sortIndex.base); - } - if (boardComponent) boardComponent.setIsDragging(false); - }, - sort(event, ui) { - const $boardCanvas = $('.board-canvas'); - const boardCanvas = $boardCanvas[0]; + card.move(currentBoard._id, newSwimlaneId, listId, sortIndex.base); + } + boardComponent.setIsDragging(false); + }, + sort(event, ui) { + const $boardCanvas = $('.board-canvas'); + const boardCanvas = $boardCanvas[0]; - if (event.pageX < 10) { // scroll to the left - boardCanvas.scrollLeft -= 15; - ui.helper[0].offsetLeft -= 15; - } - if ( - event.pageX > boardCanvas.offsetWidth - 10 && - boardCanvas.scrollLeft < $boardCanvas.data('scrollLeftMax') // don't scroll more than possible - ) { // scroll to the right - boardCanvas.scrollLeft += 15; - } - if ( - event.pageY > boardCanvas.offsetHeight - 10 && - event.pageY + boardCanvas.scrollTop < $boardCanvas.data('scrollTopMax') // don't scroll more than possible - ) { // scroll to the bottom - boardCanvas.scrollTop += 15; - } - if (event.pageY < 10) { // scroll to the top - boardCanvas.scrollTop -= 15; - } - }, - activate(event, ui) { - const $boardCanvas = $('.board-canvas'); - const boardCanvas = $boardCanvas[0]; - // scrollTopMax and scrollLeftMax only available at Firefox (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTopMax) - // https://www.it-swarm.com.de/de/javascript/so-erhalten-sie-den-maximalen-dokument-scrolltop-wert/1069126844/ - $boardCanvas.data('scrollTopMax', boardCanvas.scrollHeight - boardCanvas.clientTop); - // https://stackoverflow.com/questions/5138373/how-do-i-get-the-max-value-of-scrollleft/5704386#5704386 - $boardCanvas.data('scrollLeftMax', boardCanvas.scrollWidth - boardCanvas.clientWidth); - }, - }); - - this.autorun(() => { - if ($cards.data('uiSortable') || $cards.data('sortable')) { - if (Utils.isTouchScreenOrShowDesktopDragHandles()) { - $cards.sortable('option', 'handle', '.handle'); - } else { - $cards.sortable('option', 'handle', '.minicard'); - } - - $cards.sortable( - 'option', - 'disabled', - // Disable drag-dropping when user is not member - !Utils.canModifyBoard(), - // Not disable drag-dropping while in multi-selection mode - // MultiSelection.isActive() || !Utils.canModifyBoard(), - ); - } - }); - - // We want to re-run this function any time a card is added. - this.autorun(() => { - const currentBoardId = Tracker.nonreactive(() => { - return Session.get('currentBoard'); + if (event.pageX < 10) { // scroll to the left + boardCanvas.scrollLeft -= 15; + ui.helper[0].offsetLeft -= 15; + } + if ( + event.pageX > boardCanvas.offsetWidth - 10 && + boardCanvas.scrollLeft < $boardCanvas.data('scrollLeftMax') // don't scroll more than possible + ) { // scroll to the right + boardCanvas.scrollLeft += 15; + } + if ( + event.pageY > boardCanvas.offsetHeight - 10 && + event.pageY + boardCanvas.scrollTop < $boardCanvas.data('scrollTopMax') // don't scroll more than possible + ) { // scroll to the bottom + boardCanvas.scrollTop += 15; + } + if (event.pageY < 10) { // scroll to the top + boardCanvas.scrollTop -= 15; + } + }, + activate(event, ui) { + const $boardCanvas = $('.board-canvas'); + const boardCanvas = $boardCanvas[0]; + // scrollTopMax and scrollLeftMax only available at Firefox (https://developer.mozilla.org/en-US/docs/Web/API/Element/scrollTopMax) + // https://www.it-swarm.com.de/de/javascript/so-erhalten-sie-den-maximalen-dokument-scrolltop-wert/1069126844/ + $boardCanvas.data('scrollTopMax', boardCanvas.scrollHeight - boardCanvas.clientTop); + // https://stackoverflow.com/questions/5138373/how-do-i-get-the-max-value-of-scrollleft/5704386#5704386 + $boardCanvas.data('scrollLeftMax', boardCanvas.scrollWidth - boardCanvas.clientWidth); + }, }); - Tracker.afterFlush(() => { - $cards.find(itemsSelector).droppable({ - hoverClass: 'draggable-hover-card', - accept: '.js-member,.js-label', - drop(event, ui) { - const cardId = Blaze.getData(this)._id; - const card = ReactiveCache.getCard(cardId); - if (ui.draggable.hasClass('js-member')) { - const memberId = Blaze.getData(ui.draggable.get(0)).userId; - card.assignMember(memberId); - } else { - const labelId = Blaze.getData(ui.draggable.get(0))._id; - card.addLabel(labelId); - } - }, + this.autorun(() => { + if ($cards.data('uiSortable') || $cards.data('sortable')) { + if (Utils.isTouchScreenOrShowDesktopDragHandles()) { + $cards.sortable('option', 'handle', '.handle'); + } else { + $cards.sortable('option', 'handle', '.minicard'); + } + + $cards.sortable( + 'option', + 'disabled', + // Disable drag-dropping when user is not member + !Utils.canModifyBoard(), + // Not disable drag-dropping while in multi-selection mode + // MultiSelection.isActive() || !Utils.canModifyBoard(), + ); + } + }); + + // We want to re-run this function any time a card is added. + this.autorun(() => { + const currentBoardId = Tracker.nonreactive(() => { + return Session.get('currentBoard'); + }); + Tracker.afterFlush(() => { + $cards.find(itemsSelector).droppable({ + hoverClass: 'draggable-hover-card', + accept: '.js-member,.js-label', + drop(event, ui) { + const cardId = Blaze.getData(this)._id; + const card = ReactiveCache.getCard(cardId); + + if (ui.draggable.hasClass('js-member')) { + const memberId = Blaze.getData(ui.draggable.get(0)).userId; + card.assignMember(memberId); + } else { + const labelId = Blaze.getData(ui.draggable.get(0))._id; + card.addLabel(labelId); + } + }, + }); }); }); - }); -}); + }, -Template.list.helpers({ listWidth() { const user = ReactiveCache.getCurrentUser(); const list = Template.currentData(); if (!list) return 270; // Return default width if list is not available - + if (user) { // For logged-in users, get from user profile return user.getListWidthFromStorage(list.boardId, list._id); @@ -229,7 +223,7 @@ Template.list.helpers({ const user = ReactiveCache.getCurrentUser(); const list = Template.currentData(); if (!list) return 550; // Return default constraint if list is not available - + if (user) { // For logged-in users, get from user profile return user.getListConstraintFromStorage(list.boardId, list._id); @@ -260,93 +254,58 @@ Template.list.helpers({ return user.isAutoWidth(list.boardId); }, - collapsed() { - return Utils.getListCollapseState(this); - }, -}); - -// initializeListResize as a method on the template instance -Template.list.onCreated(function () { - const tpl = this; - - tpl.initializeListResize = function () { + initializeListResize() { // Check if we're still in a valid template context if (!Template.currentData()) { console.warn('No current template data available for list resize initialization'); return; } - + const list = Template.currentData(); - const $list = tpl.$('.js-list'); - const $resizeHandle = tpl.$('.js-list-resize-handle'); - + const $list = this.$('.js-list'); + const $resizeHandle = this.$('.js-list-resize-handle'); + // Check if elements exist if (!$list.length || !$resizeHandle.length) { console.warn('List or resize handle not found, retrying in 100ms'); Meteor.setTimeout(() => { - if (!tpl.isDestroyed) { - tpl.initializeListResize(); + if (!this.isDestroyed) { + this.initializeListResize(); } }, 100); return; } - - // Helper to get autoWidth state - const getAutoWidth = () => { - const user = ReactiveCache.getCurrentUser(); - const listData = Template.currentData(); - if (!user) return false; - return user.isAutoWidth(listData.boardId); - }; - - // Reactively show/hide resize handle based on collapse and auto-width state - tpl.autorun(() => { - const isAutoWidth = getAutoWidth(); - const isCollapsed = Utils.getListCollapseState(list); - if (isCollapsed || isAutoWidth) { - $resizeHandle.hide(); - } else { - $resizeHandle.show(); - } - }); + + + // Only enable resize for non-collapsed, non-auto-width lists + const isAutoWidth = this.autoWidth(); + if (list.collapsed || isAutoWidth) { + $resizeHandle.hide(); + return; + } let isResizing = false; let startX = 0; let startWidth = 0; - let minWidth = 270; // Minimum width matching system default - - // Get listConstraint value - const getListConstraint = () => { - const user = ReactiveCache.getCurrentUser(); - const listData = Template.currentData(); - if (!listData) return 550; - if (user) { - return user.getListConstraintFromStorage(listData.boardId, listData._id); - } - try { - const stored = localStorage.getItem('wekan-list-constraints'); - if (stored) { - const constraints = JSON.parse(stored); - if (constraints[listData.boardId] && constraints[listData.boardId][listData._id]) { - return constraints[listData.boardId][listData._id]; - } - } - } catch (e) {} - return 550; - }; + let minWidth = 100; // Minimum width as defined in the existing code + let maxWidth = this.listConstraint() || 1000; // Use constraint as max width + let listConstraint = this.listConstraint(); // Store constraint value for use in event handlers + const component = this; // Store reference to component for use in event handlers const startResize = (e) => { isResizing = true; startX = e.pageX || e.originalEvent.touches[0].pageX; startWidth = $list.outerWidth(); - + + // Add visual feedback $list.addClass('list-resizing'); $('body').addClass('list-resizing-active'); - + + // Prevent text selection during resize $('body').css('user-select', 'none'); - + e.preventDefault(); e.stopPropagation(); }; @@ -355,11 +314,11 @@ Template.list.onCreated(function () { if (!isResizing) { return; } - + const currentX = e.pageX || e.originalEvent.touches[0].pageX; const deltaX = currentX - startX; - const newWidth = Math.max(minWidth, startWidth + deltaX); - + const newWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + deltaX)); + // Apply the new width immediately for real-time feedback $list[0].style.setProperty('--list-width', `${newWidth}px`); $list[0].style.setProperty('width', `${newWidth}px`); @@ -369,22 +328,22 @@ Template.list.onCreated(function () { $list[0].style.setProperty('flex-basis', 'auto'); $list[0].style.setProperty('flex-grow', '0'); $list[0].style.setProperty('flex-shrink', '0'); - + + e.preventDefault(); e.stopPropagation(); }; const stopResize = (e) => { if (!isResizing) return; - + isResizing = false; - + // Calculate final width const currentX = e.pageX || e.originalEvent.touches[0].pageX; const deltaX = currentX - startX; - const finalWidth = Math.max(minWidth, startWidth + deltaX); - const listConstraint = getListConstraint(); - + const finalWidth = Math.max(minWidth, Math.min(maxWidth, startWidth + deltaX)); + // Ensure the final width is applied $list[0].style.setProperty('--list-width', `${finalWidth}px`); $list[0].style.setProperty('width', `${finalWidth}px`); @@ -394,19 +353,23 @@ Template.list.onCreated(function () { $list[0].style.setProperty('flex-basis', 'auto'); $list[0].style.setProperty('flex-grow', '0'); $list[0].style.setProperty('flex-shrink', '0'); - + // Remove visual feedback but keep the width $list.removeClass('list-resizing'); $('body').removeClass('list-resizing-active'); $('body').css('user-select', ''); - + + // Keep the CSS custom property for persistent width + // The CSS custom property will remain on the element to maintain the width + // Save the new width using the existing system const boardId = list.boardId; const listId = list._id; - + + // Use the new storage method that handles both logged-in and non-logged-in users if (process.env.DEBUG === 'true') { } - + const currentUser = ReactiveCache.getCurrentUser(); if (currentUser) { // For logged-in users, use server method @@ -424,32 +387,32 @@ Template.list.onCreated(function () { // Save list width const storedWidths = localStorage.getItem('wekan-list-widths'); let widths = storedWidths ? JSON.parse(storedWidths) : {}; - + if (!widths[boardId]) { widths[boardId] = {}; } widths[boardId][listId] = finalWidth; - + localStorage.setItem('wekan-list-widths', JSON.stringify(widths)); - + // Save list constraint const storedConstraints = localStorage.getItem('wekan-list-constraints'); let constraints = storedConstraints ? JSON.parse(storedConstraints) : {}; - + if (!constraints[boardId]) { constraints[boardId] = {}; } constraints[boardId][listId] = listConstraint; - + localStorage.setItem('wekan-list-constraints', JSON.stringify(constraints)); - + if (process.env.DEBUG === 'true') { } } catch (e) { console.warn('Error saving list width/constraint to localStorage:', e); } } - + e.preventDefault(); }; @@ -457,21 +420,22 @@ Template.list.onCreated(function () { $resizeHandle.on('mousedown', startResize); $(document).on('mousemove', doResize); $(document).on('mouseup', stopResize); - + // Touch events for mobile $resizeHandle.on('touchstart', startResize, { passive: false }); $(document).on('touchmove', doResize, { passive: false }); $(document).on('touchend', stopResize, { passive: false }); - + + // Prevent dragscroll interference $resizeHandle.on('mousedown', (e) => { e.stopPropagation(); }); - - // Reactively update resize handle visibility when auto-width or collapse changes - tpl.autorun(() => { - const collapsed = Utils.getListCollapseState(list); - if (getAutoWidth() || collapsed) { + + + // Reactively update resize handle visibility when auto-width changes + component.autorun(() => { + if (component.autoWidth()) { $resizeHandle.hide(); } else { $resizeHandle.show(); @@ -479,14 +443,14 @@ Template.list.onCreated(function () { }); // Clean up on component destruction - tpl.view.onViewDestroyed(() => { + component.onDestroyed(() => { $(document).off('mousemove', doResize); $(document).off('mouseup', stopResize); $(document).off('touchmove', doResize); $(document).off('touchend', stopResize); }); - }; -}); + }, +}).register('list'); Template.miniList.events({ 'click .js-select-list'() { @@ -494,7 +458,3 @@ Template.miniList.events({ Session.set('currentList', listId); }, }); - -// NOTE: Collapsed list drag-reorder was previously here but referenced -// boardComponent from an outer scope. If needed, this should be moved -// into Template.list.onRendered where boardComponent is available. diff --git a/client/components/lists/listBody.jade b/client/components/lists/listBody.jade index 3d23a49ce..e08684a4f 100644 --- a/client/components/lists/listBody.jade +++ b/client/components/lists/listBody.jade @@ -2,8 +2,9 @@ template(name="listBody") unless collapsed .list-body(class="{{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}") .minicards.clearfix.js-minicards(class="{{#if reachedWipLimit}}js-list-full{{/if}}") - +inlinedForm(autoclose=false position="top") - +addCardForm(listId=_id position="top") + if cards.length + +inlinedForm(autoclose=false position="top") + +addCardForm(listId=_id position="top") ul.sidebar-list each customFieldsSum li @@ -25,15 +26,13 @@ template(name="listBody") +minicard(this) if (showSpinner (idOrNull ../../_id)) +spinnerList + if canSeeAddCard +inlinedForm(autoclose=false position="bottom") +addCardForm(listId=_id position="bottom") else a.open-minicard-composer.js-card-composer.js-open-inlined-form(title="{{_ 'add-card-to-bottom-of-list'}}") - i.fa.fa-plus - | {{_ 'add-card'}} - +inlinedForm(autoclose=false position="bottom") - +addCardForm(listId=_id position="bottom") + | ➕ template(name="spinnerList") .sk-spinner.sk-spinner-list( @@ -55,8 +54,7 @@ template(name="addCardForm") .add-controls.clearfix button.primary.confirm(type="submit") {{_ 'add'}} - a.js-close-inlined-form - i.fa.fa-times-thin + a.js-close-inlined-form | ❌ .add-controls.clearfix unless currentBoard.isTemplatesBoard unless currentBoard.isTemplateBoard @@ -87,19 +85,16 @@ template(name="linkCardPopup") label {{_ 'swimlanes'}}: select.js-select-swimlanes - option(value="") {{_ 'custom-field-dropdown-none'}} each swimlanes option(value="{{_id}}") {{isTitleDefault title}} label {{_ 'lists'}}: select.js-select-lists - option(value="") {{_ 'custom-field-dropdown-none'}} each lists option(value="{{_id}}") {{isTitleDefault title}} label {{_ 'cards'}}: select.js-select-cards - option(value="") {{_ 'custom-field-dropdown-none'}} each cards option(value="{{getRealId}}") {{getTitle}} diff --git a/client/components/lists/listBody.js b/client/components/lists/listBody.js index d69815fba..529c70fc8 100644 --- a/client/components/lists/listBody.js +++ b/client/components/lists/listBody.js @@ -1,73 +1,48 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; -import { getSpinnerName, getSpinnerTemplate } from '/client/lib/spinner'; -import getSlug from 'limax'; +import { Spinner } from '/client/lib/spinner'; const subManager = new SubsManager(); const InfiniteScrollIter = 10; -Template.listBody.onCreated(function () { - // for infinite scrolling - this.cardlimit = new ReactiveVar(InfiniteScrollIter); +BlazeComponent.extendComponent({ + onCreated() { + // for infinite scrolling + this.cardlimit = new ReactiveVar(InfiniteScrollIter); + }, - this.openForm = (options) => { + mixins() { + return []; + }, + + customFieldsSum() { + const ret = ReactiveCache.getCustomFields({ + boardIds: { $in: [Session.get('currentBoard')] }, + showSumAtTopOfList: true, + }); + return ret; + }, + + openForm(options) { options = options || {}; options.position = options.position || 'top'; - // Find inlinedForm template instances by DOM traversal - const formEls = this.findAll('.js-inlined-form'); - let formInstance = null; - for (const el of formEls) { - const view = Blaze.getView(el, 'Template.inlinedForm'); - const inst = view?.templateInstance?.(); - if (inst) { - const data = Blaze.getData(el); - if (data?.position === options.position) { - formInstance = inst; - break; - } - if (!formInstance) formInstance = inst; // fallback to first - } - } - if (formInstance) { - formInstance.isOpen.set(true); - } - }; - - this.cardFormComponent = () => { - // Find addCardForm template instance by DOM traversal - const formEl = this.find('.js-composer'); - if (formEl) { - const view = Blaze.getView(formEl, 'Template.addCardForm'); - return view?.templateInstance?.() || null; - } - return null; - }; - - this.scrollToBottom = () => { - const container = this.firstNode; - $(container).animate({ - scrollTop: container.scrollHeight, + const forms = this.childComponents('inlinedForm'); + let form = forms.find(component => { + return component.data().position === options.position; }); - }; + if (!form && forms.length > 0) { + form = forms[0]; + } + form.open(); + }, - this.idOrNull = (swimlaneId) => { - const data = Template.currentData(); - if ( - Utils.boardView() === 'board-view-swimlanes' || - data.board().isTemplatesBoard() - ) - return swimlaneId; - return undefined; - }; - - this.addCard = (evt) => { + addCard(evt) { evt.preventDefault(); const firstCardDom = this.find('.js-minicard:first'); const lastCardDom = this.find('.js-minicard:last'); const textarea = $(evt.currentTarget).find('textarea'); - const position = Template.currentData().position; + const position = this.currentData().position; const title = textarea.val().trim(); let sortIndex; @@ -82,15 +57,15 @@ Template.listBody.onCreated(function () { const labelIds = formComponent.labels.get(); const customFields = formComponent.customFields.get(); - const data = Template.currentData(); - const board = data.board(); + const board = this.data().board(); let linkedId = ''; let swimlaneId = ''; let cardType = 'cardType-card'; if (title) { if (board.isTemplatesBoard()) { - const swimlaneEl = this.$('.js-minicards').closest('.swimlane').get(0); - swimlaneId = swimlaneEl && Blaze.getData(swimlaneEl)?._id; // Always swimlanes view + swimlaneId = this.parentComponent() + .parentComponent() + .data()._id; // Always swimlanes view const swimlane = ReactiveCache.getSwimlane(swimlaneId); // If this is the card templates swimlane, insert a card template if (swimlane.isCardTemplatesSwimlane()) cardType = 'template-card'; @@ -107,10 +82,10 @@ Template.listBody.onCreated(function () { }); cardType = 'cardType-linkedBoard'; } - } else if (Utils.boardView() === 'board-view-swimlanes') { - const swimlaneEl2 = this.$('.js-minicards').closest('.swimlane').get(0); - swimlaneId = swimlaneEl2 && Blaze.getData(swimlaneEl2)?._id; - } + } else if (Utils.boardView() === 'board-view-swimlanes') + swimlaneId = this.parentComponent() + .parentComponent() + .data()._id; else if ( Utils.boardView() === 'board-view-lists' || Utils.boardView() === 'board-view-cal' || @@ -125,7 +100,7 @@ Template.listBody.onCreated(function () { members, labelIds, customFields, - listId: data._id, + listId: this.data()._id, boardId: board._id, sort: sortIndex, swimlaneId, @@ -137,7 +112,7 @@ Template.listBody.onCreated(function () { // if the displayed card count is less than the total cards in the list, // we need to increment the displayed card count to prevent the spinner // to appear - const cardCount = data + const cardCount = this.data() .cards(this.idOrNull(swimlaneId)) .length; if (this.cardlimit.get() < cardCount) { @@ -157,110 +132,73 @@ Template.listBody.onCreated(function () { this.scrollToBottom(); } } - }; + }, - this.clickOnMiniCard = (evt) => { + cardFormComponent() { + for (const inlinedForm of this.childComponents('inlinedForm')) { + const [addCardForm] = inlinedForm.childComponents('addCardForm'); + if (addCardForm) { + return addCardForm; + } + } + return null; + }, + + scrollToBottom() { + const container = this.firstNode(); + $(container).animate({ + scrollTop: container.scrollHeight, + }); + }, + + clickOnMiniCard(evt) { if (MultiSelection.isActive() || evt.shiftKey) { evt.stopImmediatePropagation(); evt.preventDefault(); const methodName = evt.shiftKey ? 'toggleRange' : 'toggle'; - MultiSelection[methodName](Template.currentData()._id); + MultiSelection[methodName](this.currentData()._id); // If the card is already selected, we want to de-select it. // XXX We should probably modify the minicard href attribute instead of // overwriting the event in case the card is already selected. } else if (Utils.isMiniScreen()) { evt.preventDefault(); - Session.set('popupCardId', Template.currentData()._id); + Session.set('popupCardId', this.currentData()._id); this.cardDetailsPopup(evt); - } else if (Session.equals('currentCard', Template.currentData()._id)) { + } else if (Session.equals('currentCard', this.currentData()._id)) { evt.stopImmediatePropagation(); evt.preventDefault(); Utils.goBoardId(Session.get('currentBoard')); - } else { - // Allow normal href navigation, but if it's the same card URL, - // we'll handle it by directly setting the session - evt.preventDefault(); - const card = Template.currentData(); - Session.set('currentCard', card._id); - const openCards = Session.get('openCards') || []; - if (!openCards.includes(card._id)) { - Session.set('openCards', [...openCards, card._id]); - } } - }; + }, - this.toggleMultiSelection = (evt) => { + cardIsSelected() { + return Session.equals('currentCard', this.currentData()._id); + }, + + toggleMultiSelection(evt) { evt.stopPropagation(); evt.preventDefault(); - MultiSelection.toggle(Template.currentData()._id); - }; - - this.cardDetailsPopup = (event) => { - if (!Popup.isOpen()) { - Popup.open("cardDetails")(event); - } - }; -}); - -Template.listBody.helpers({ - idOrNull(swimlaneId) { - return Template.instance().idOrNull(swimlaneId); + MultiSelection.toggle(this.currentData()._id); }, - customFieldsSum() { - const list = Template.currentData(); - if (!list) return []; - const boardId = Session.get('currentBoard'); - const fields = ReactiveCache.getCustomFields({ - boardIds: { $in: [boardId] }, - showSumAtTopOfList: true, - }); - if (!fields || !fields.length) return []; - - const cards = ReactiveCache.getCards({ - listId: list._id, - archived: false, - }); - - const result = fields.map(field => { - let sum = 0; - if (cards && cards.length) { - cards.forEach(card => { - const cfs = (card.customFields || []); - const cf = cfs.find(f => f && f._id === field._id); - if (!cf || cf.value === null || cf.value === undefined) return; - let v = cf.value; - if (typeof v === 'string') { - // try to parse string numbers, accept comma decimal - const parsed = parseFloat(v.replace(',', '.')); - if (isNaN(parsed)) return; - v = parsed; - } - if (typeof v === 'number' && isFinite(v)) { - sum += v; - } - }); - } - return { - _id: field._id, - name: field.name, - type: field.type, - settings: field.settings || {}, - value: sum, - }; - }); - - return result; + idOrNull(swimlaneId) { + if ( + Utils.boardView() === 'board-view-swimlanes' || + this.data() + .board() + .isTemplatesBoard() + ) + return swimlaneId; + return undefined; }, cardsWithLimit(swimlaneId) { - const tpl = Template.instance(); - const limit = tpl.cardlimit.get(); + const limit = this.cardlimit.get(); const defaultSort = { sort: 1 }; const sortBy = Session.get('sortBy') ? Session.get('sortBy') : defaultSort; const selector = { - listId: Template.currentData()._id, + listId: this.currentData()._id, archived: false, }; if (swimlaneId) selector.swimlaneId = swimlaneId; @@ -273,19 +211,13 @@ Template.listBody.helpers({ }, showSpinner(swimlaneId) { - const tpl = Template.instance(); const list = Template.currentData(); - return list.cards(swimlaneId).length > tpl.cardlimit.get(); + return list.cards(swimlaneId).length > this.cardlimit.get(); }, canSeeAddCard() { - const tpl = Template.instance(); - const list = Template.currentData(); - const reachedWipLimit = !list.getWipLimit('soft') && - list.getWipLimit('enabled') && - list.getWipLimit('value') <= list.cards().length; return ( - !reachedWipLimit && + !this.reachedWipLimit() && Utils.canModifyCard() ); }, @@ -304,38 +236,23 @@ Template.listBody.helpers({ return user && user.isVerticalScrollbars(); }, - cardIsSelected() { - return Session.equals('currentCard', Template.currentData()._id); - }, - - formattedCurrencyCustomFieldValue(val) { - // `this` is the custom field sum object from customFieldsSum each-iteration - const field = this || {}; - const code = (field.settings && field.settings.currencyCode) || 'USD'; - try { - const n = typeof val === 'number' ? val : parseFloat(val); - if (!isFinite(n)) return val; - return new Intl.NumberFormat(undefined, { style: 'currency', currency: code }).format(n); - } catch (e) { - return `${code} ${val}`; + cardDetailsPopup(event) { + if (!Popup.isOpen()) { + Popup.open("cardDetails")(event); } }, -}); -Template.listBody.events({ - 'click .js-minicard'(evt, tpl) { - tpl.clickOnMiniCard(evt); + events() { + return [ + { + 'click .js-minicard': this.clickOnMiniCard, + 'click .js-toggle-multi-selection': this.toggleMultiSelection, + 'click .open-minicard-composer': this.scrollToBottom, + submit: this.addCard, + }, + ]; }, - 'click .js-toggle-multi-selection'(evt, tpl) { - tpl.toggleMultiSelection(evt); - }, - 'click .open-minicard-composer'(evt, tpl) { - tpl.scrollToBottom(); - }, - submit(evt, tpl) { - tpl.addCard(evt); - }, -}); +}).register('listBody'); function toggleValueInReactiveArray(reactiveValue, value) { const array = reactiveValue.get(); @@ -348,30 +265,42 @@ function toggleValueInReactiveArray(reactiveValue, value) { reactiveValue.set(array); } -Template.addCardForm.onCreated(function () { - this.labels = new ReactiveVar([]); - this.members = new ReactiveVar([]); - this.customFields = new ReactiveVar([]); +BlazeComponent.extendComponent({ + onCreated() { + this.labels = new ReactiveVar([]); + this.members = new ReactiveVar([]); + this.customFields = new ReactiveVar([]); - const currentBoardId = Session.get('currentBoard'); - const arr = []; - _.forEach( - ReactiveCache.getBoard(currentBoardId) - .customFields(), - function (field) { - if (field.automaticallyOnCard || field.alwaysOnCard) - arr.push({ _id: field._id, value: null }); - }, - ); - this.customFields.set(arr); + const currentBoardId = Session.get('currentBoard'); + arr = []; + _.forEach( + ReactiveCache.getBoard(currentBoardId) + .customFields(), + function (field) { + if (field.automaticallyOnCard || field.alwaysOnCard) + arr.push({ _id: field._id, value: null }); + }, + ); + this.customFields.set(arr); + }, - this.reset = () => { + reset() { this.labels.set([]); this.members.set([]); this.customFields.set([]); - }; + }, - this.pressKey = (evt) => { + getLabels() { + const currentBoardId = Session.get('currentBoard'); + if (ReactiveCache.getBoard(currentBoardId).labels) { + return ReactiveCache.getBoard(currentBoardId).labels.filter(label => { + return this.labels.get().indexOf(label._id) > -1; + }); + } + return false; + }, + + pressKey(evt) { // Pressing Enter should submit the card if (evt.keyCode === 13 && !evt.shiftKey) { evt.preventDefault(); @@ -388,7 +317,7 @@ Template.addCardForm.onCreated(function () { // Prevent custom focus movement on Tab key for accessibility // evt.preventDefault(); const isReverse = evt.shiftKey; - const list = $(`#js-list-${Template.currentData().listId}`); + const list = $(`#js-list-${this.data().listId}`); const listSelector = '.js-list:not(.js-list-composer)'; let nextList = list[isReverse ? 'prev' : 'next'](listSelector).get(0); // If there is no next list, loop back to the beginning. @@ -396,165 +325,134 @@ Template.addCardForm.onCreated(function () { nextList = $(listSelector + (isReverse ? ':last' : ':first')).get(0); } - const nextListView = Blaze.getView(nextList, 'Template.list'); - const nextListInstance = nextListView?.templateInstance?.(); - if (nextListInstance) { - nextListInstance.openForm({ - position: Template.currentData().position, - }); - } - } - }; -}); - -Template.addCardForm.helpers({ - members() { - return Template.instance().members; - }, - getLabels() { - const tpl = Template.instance(); - const currentBoardId = Session.get('currentBoard'); - if (ReactiveCache.getBoard(currentBoardId).labels) { - return ReactiveCache.getBoard(currentBoardId).labels.filter(label => { - return tpl.labels.get().indexOf(label._id) > -1; + BlazeComponent.getComponentForElement(nextList).openForm({ + position: this.data().position, }); } - return false; }, -}); -Template.addCardForm.events({ - keydown(evt, tpl) { - tpl.pressKey(evt); + events() { + return [ + { + keydown: this.pressKey, + 'click .js-link': Popup.open('linkCard'), + 'click .js-search': Popup.open('searchElement'), + 'click .js-card-template': Popup.open('searchElement'), + }, + ]; }, - 'click .js-link': Popup.open('linkCard'), - 'click .js-search': Popup.open('searchElement'), - 'click .js-card-template': Popup.open('searchElement'), -}); -Template.addCardForm.onRendered(function () { - const tpl = this; - const $textarea = this.$('textarea'); + onRendered() { + const editor = this; + const $textarea = this.$('textarea'); - autosize($textarea); + autosize($textarea); - $textarea.escapeableTextComplete( - [ - // User mentions + $textarea.escapeableTextComplete( + [ + // User mentions + { + match: /\B@([\w.-]*)$/, + search(term, callback) { + const currentBoard = Utils.getCurrentBoard(); + callback( + $.map(currentBoard.activeMembers(), member => { + const user = ReactiveCache.getUser(member.userId); + return user.username.indexOf(term) === 0 ? user : null; + }), + ); + }, + template(user) { + if (user.profile && user.profile.fullname) { + return (user.username + " (" + user.profile.fullname + ")"); + } + return user.username; + }, + replace(user) { + toggleValueInReactiveArray(editor.members, user._id); + return ''; + }, + index: 1, + }, + + // Labels + { + match: /\B#(\w*)$/, + search(term, callback) { + const currentBoard = Utils.getCurrentBoard(); + callback( + $.map(currentBoard.labels, label => { + if (label.name == undefined) { + label.name = ""; + } + if ( + label.name.indexOf(term) > -1 || + label.color.indexOf(term) > -1 + ) { + return label; + } + return null; + }), + ); + }, + template(label) { + return Blaze.toHTMLWithData(Template.autocompleteLabelLine, { + hasNoName: !label.name, + colorName: label.color, + labelName: label.name || label.color, + }); + }, + replace(label) { + toggleValueInReactiveArray(editor.labels, label._id); + return ''; + }, + index: 1, + }, + ], { - match: /\B@([\w.-]*)$/, - search(term, callback) { - const currentBoard = Utils.getCurrentBoard(); - callback( - $.map(currentBoard.activeMembers(), member => { - const user = ReactiveCache.getUser(member.userId); - return user.username.indexOf(term) === 0 ? user : null; - }), - ); + // When the autocomplete menu is shown we want both a press of both `Tab` + // or `Enter` to validation the auto-completion. We also need to stop the + // event propagation to prevent the card from submitting (on `Enter`) or + // going on the next column (on `Tab`). + /* + onKeydown(evt, commands) { + // Prevent custom focus movement on Tab key for accessibility + // if (evt.keyCode === 9 || evt.keyCode === 13) { + // evt.stopPropagation(); + // return commands.KEY_ENTER; + //} + return null; }, - template(user) { - if (user.profile && user.profile.fullname) { - return (user.username + " (" + user.profile.fullname + ")"); - } - return user.username; - }, - replace(user) { - toggleValueInReactiveArray(tpl.members, user._id); - return ''; - }, - index: 1, + */ }, + ); + }, +}).register('addCardForm'); - // Labels - { - match: /\B#(\w*)$/, - search(term, callback) { - const currentBoard = Utils.getCurrentBoard(); - callback( - $.map(currentBoard.labels, label => { - if (label.name == undefined) { - label.name = ""; - } - if ( - label.name.indexOf(term) > -1 || - label.color.indexOf(term) > -1 - ) { - return label; - } - return null; - }), - ); - }, - template(label) { - return Blaze.toHTMLWithData(Template.autocompleteLabelLine, { - hasNoName: !label.name, - colorName: label.color, - labelName: label.name || label.color, - }); - }, - replace(label) { - toggleValueInReactiveArray(tpl.labels, label._id); - return ''; - }, - index: 1, - }, - ], - { - // When the autocomplete menu is shown we want both a press of both `Tab` - // or `Enter` to validation the auto-completion. We also need to stop the - // event propagation to prevent the card from submitting (on `Enter`) or - // going on the next column (on `Tab`). - /* - onKeydown(evt, commands) { - // Prevent custom focus movement on Tab key for accessibility - // if (evt.keyCode === 9 || evt.keyCode === 13) { - // evt.stopPropagation(); - // return commands.KEY_ENTER; - //} - return null; - }, - */ - }, - ); -}); +BlazeComponent.extendComponent({ + onCreated() { + this.selectedBoardId = new ReactiveVar(''); + this.selectedSwimlaneId = new ReactiveVar(''); + this.selectedListId = new ReactiveVar(''); -Template.linkCardPopup.onCreated(function () { - this.selectedBoardId = new ReactiveVar(''); - this.selectedSwimlaneId = new ReactiveVar(''); - this.selectedListId = new ReactiveVar(''); + this.boardId = Session.get('currentBoard'); + // In order to get current board info + subManager.subscribe('board', this.boardId, false); + this.board = ReactiveCache.getBoard(this.boardId); + // List where to insert card + this.list = $(Popup._getTopStack().openerElement).closest('.js-list'); + this.listId = Blaze.getData(this.list[0])._id; + // Swimlane where to insert card + const swimlane = $(Popup._getTopStack().openerElement).closest( + '.js-swimlane', + ); + this.swimlaneId = ''; + if (Utils.boardView() === 'board-view-swimlanes') + this.swimlaneId = Blaze.getData(swimlane[0])._id; + else if (Utils.boardView() === 'board-view-lists' || !Utils.boardView) + this.swimlaneId = ReactiveCache.getSwimlane({ boardId: this.boardId })._id; + }, - this.boardId = Session.get('currentBoard'); - // In order to get current board info - subManager.subscribe('board', this.boardId, false); - this.board = ReactiveCache.getBoard(this.boardId); - // List where to insert card - this.list = $(Popup._getTopStack().openerElement).closest('.js-list'); - this.listId = Blaze.getData(this.list[0])._id; - // Swimlane where to insert card - const swimlane = $(Popup._getTopStack().openerElement).closest( - '.js-swimlane', - ); - this.swimlaneId = ''; - if (Utils.boardView() === 'board-view-swimlanes') - this.swimlaneId = Blaze.getData(swimlane[0])._id; - else if (Utils.boardView() === 'board-view-lists' || !Utils.boardView) - this.swimlaneId = ReactiveCache.getSwimlane({ boardId: this.boardId })._id; - - this.getSortIndex = () => { - const position = Template.currentData().position; - let ret; - if (position === 'top') { - const firstCardDom = this.list.find('.js-minicard:first')[0]; - ret = Utils.calculateIndex(null, firstCardDom).base; - } else if (position === 'bottom') { - const lastCardDom = this.list.find('.js-minicard:last')[0]; - ret = Utils.calculateIndex(lastCardDom, null).base; - } - return ret; - }; -}); - -Template.linkCardPopup.helpers({ boards() { const ret = ReactiveCache.getBoards( { @@ -571,63 +469,148 @@ Template.linkCardPopup.helpers({ }, swimlanes() { - const tpl = Template.instance(); - if (!tpl.selectedBoardId.get()) { + if (!this.selectedBoardId.get()) { return []; } - const board = ReactiveCache.getBoard(tpl.selectedBoardId.get()); + const board = ReactiveCache.getBoard(this.selectedBoardId.get()); if (!board) { return []; } - + // Ensure default swimlane exists board.getDefaultSwimline(); - + const swimlanes = ReactiveCache.getSwimlanes( { - boardId: tpl.selectedBoardId.get() + boardId: this.selectedBoardId.get() }, { sort: { sort: 1 }, }); + if (swimlanes.length) + this.selectedSwimlaneId.set(swimlanes[0]._id); return swimlanes; }, lists() { - const tpl = Template.instance(); - if (!tpl.selectedBoardId.get()) { + if (!this.selectedBoardId.get()) { return []; } const lists = ReactiveCache.getLists( { - boardId: tpl.selectedBoardId.get() + boardId: this.selectedBoardId.get() }, { sort: { sort: 1 }, }); + if (lists.length) this.selectedListId.set(lists[0]._id); return lists; }, cards() { - const tpl = Template.instance(); - if (!tpl.board) { + if (!this.board) { return []; } - const ownCardsIds = tpl.board.cards().map(card => card.getRealId()); - const selector = { + const ownCardsIds = this.board.cards().map(card => card.getRealId()); + const ret = ReactiveCache.getCards( + { + boardId: this.selectedBoardId.get(), + swimlaneId: this.selectedSwimlaneId.get(), + listId: this.selectedListId.get(), archived: false, linkedId: { $nin: ownCardsIds }, _id: { $nin: ownCardsIds }, type: { $nin: ['template-card'] }, - }; - if (tpl.selectedBoardId.get()) selector.boardId = tpl.selectedBoardId.get(); - if (tpl.selectedSwimlaneId.get()) selector.swimlaneId = tpl.selectedSwimlaneId.get(); - if (tpl.selectedListId.get()) selector.listId = tpl.selectedListId.get(); - - const ret = ReactiveCache.getCards(selector, { sort: { sort: 1 } }); + }, + { + sort: { sort: 1 }, + }); return ret; }, + getSortIndex() { + const position = this.currentData().position; + let ret; + if (position === 'top') { + const firstCardDom = this.list.find('.js-minicard:first')[0]; + ret = Utils.calculateIndex(null, firstCardDom).base; + } else if (position === 'bottom') { + const lastCardDom = this.list.find('.js-minicard:last')[0]; + ret = Utils.calculateIndex(lastCardDom, null).base; + } + return ret; + }, + + events() { + return [ + { + 'change .js-select-boards'(evt) { + subManager.subscribe('board', $(evt.currentTarget).val(), false); + this.selectedBoardId.set($(evt.currentTarget).val()); + }, + 'change .js-select-swimlanes'(evt) { + this.selectedSwimlaneId.set($(evt.currentTarget).val()); + }, + 'change .js-select-lists'(evt) { + this.selectedListId.set($(evt.currentTarget).val()); + }, + 'click .js-done'(evt) { + // LINK CARD + evt.stopPropagation(); + evt.preventDefault(); + const linkedId = $('.js-select-cards option:selected').val(); + if (!linkedId) { + Popup.back(); + return; + } + const nextCardNumber = this.board.getNextCardNumber(); + const sortIndex = this.getSortIndex(); + const _id = Cards.insert({ + title: $('.js-select-cards option:selected').text(), //dummy + listId: this.listId, + swimlaneId: this.swimlaneId, + boardId: this.boardId, + sort: sortIndex, + type: 'cardType-linkedCard', + linkedId, + cardNumber: nextCardNumber, + }); + Filter.addException(_id); + Popup.back(); + }, + 'click .js-link-board'(evt) { + //LINK BOARD + evt.stopPropagation(); + evt.preventDefault(); + const impBoardId = $('.js-select-boards option:selected').val(); + if ( + !impBoardId || + ReactiveCache.getCard({ linkedId: impBoardId, archived: false }) + ) { + Popup.back(); + return; + } + const nextCardNumber = this.board.getNextCardNumber(); + const sortIndex = this.getSortIndex(); + const _id = Cards.insert({ + title: $('.js-select-boards option:selected').text(), //dummy + listId: this.listId, + swimlaneId: this.swimlaneId, + boardId: this.boardId, + sort: sortIndex, + type: 'cardType-linkedBoard', + linkedId: impBoardId, + cardNumber: nextCardNumber, + }); + Filter.addException(_id); + Popup.back(); + }, + }, + ]; + }, +}).register('linkCardPopup'); + +Template.linkCardPopup.helpers({ isTitleDefault(title) { // https://github.com/wekan/wekan/issues/4763 // https://github.com/wekan/wekan/issues/4742 @@ -652,142 +635,65 @@ Template.linkCardPopup.helpers({ }, }); -Template.linkCardPopup.events({ - 'change .js-select-boards'(evt, tpl) { - const val = $(evt.currentTarget).val(); - subManager.subscribe('board', val, false); - // Clear selections to allow linking only board or re-choose swimlane/list - tpl.selectedSwimlaneId.set(''); - tpl.selectedListId.set(''); - tpl.selectedBoardId.set(val); +BlazeComponent.extendComponent({ + mixins() { + return []; }, - 'change .js-select-swimlanes'(evt, tpl) { - tpl.selectedSwimlaneId.set($(evt.currentTarget).val()); - }, - 'change .js-select-lists'(evt, tpl) { - tpl.selectedListId.set($(evt.currentTarget).val()); - }, - 'click .js-done'(evt, tpl) { - // LINK CARD - evt.stopPropagation(); - evt.preventDefault(); - const linkedId = $('.js-select-cards option:selected').val(); - if (!linkedId) { - Popup.back(); - return; - } - const nextCardNumber = tpl.board.getNextCardNumber(); - const sortIndex = tpl.getSortIndex(); - const _id = Cards.insert({ - title: $('.js-select-cards option:selected').text(), //dummy - listId: tpl.listId, - swimlaneId: tpl.swimlaneId, - boardId: tpl.boardId, - sort: sortIndex, - type: 'cardType-linkedCard', - linkedId, - cardNumber: nextCardNumber, - }); - Filter.addException(_id); - Popup.back(); - }, - 'click .js-link-board'(evt, tpl) { - //LINK BOARD - evt.stopPropagation(); - evt.preventDefault(); - const impBoardId = $('.js-select-boards option:selected').val(); - if ( - !impBoardId || - ReactiveCache.getCard({ linkedId: impBoardId, archived: false }) - ) { - Popup.back(); - return; - } - const nextCardNumber = tpl.board.getNextCardNumber(); - const sortIndex = tpl.getSortIndex(); - const _id = Cards.insert({ - title: $('.js-select-boards option:selected').text(), //dummy - listId: tpl.listId, - swimlaneId: tpl.swimlaneId, - boardId: tpl.boardId, - sort: sortIndex, - type: 'cardType-linkedBoard', - linkedId: impBoardId, - cardNumber: nextCardNumber, - }); - Filter.addException(_id); - Popup.back(); - }, -}); -Template.searchElementPopup.onCreated(function () { - this.isCardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass( - 'js-card-template', - ); - this.isListTemplateSearch = $(Popup._getTopStack().openerElement).hasClass( - 'js-list-template', - ); - this.isSwimlaneTemplateSearch = $( - Popup._getTopStack().openerElement, - ).hasClass('js-open-add-swimlane-menu'); - this.isBoardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass( - 'js-add-board', - ); - this.isTemplateSearch = - this.isCardTemplateSearch || - this.isListTemplateSearch || - this.isSwimlaneTemplateSearch || - this.isBoardTemplateSearch; - - this.board = {}; - if (this.isTemplateSearch) { - const boardId = (ReactiveCache.getCurrentUser().profile || {}).templatesBoardId; - if (boardId) { - subManager.subscribe('board', boardId, false); - this.board = ReactiveCache.getBoard(boardId); - } - } else { - this.board = Utils.getCurrentBoard(); - } - if (!this.board) { - Popup.back(); - return; - } - this.boardId = this.board._id; - // Subscribe to this board - subManager.subscribe('board', this.boardId, false); - this.selectedBoardId = new ReactiveVar(this.boardId); - this.list = $(Popup._getTopStack().openerElement).closest('.js-list'); - - if (!this.isBoardTemplateSearch) { - this.swimlaneId = ''; - // Swimlane where to insert card - const swimlane = $(Popup._getTopStack().openerElement).parents( - '.js-swimlane', + onCreated() { + this.isCardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass( + 'js-card-template', ); - if (Utils.boardView() === 'board-view-swimlanes') - this.swimlaneId = Blaze.getData(swimlane[0])._id; - else this.swimlaneId = ReactiveCache.getSwimlane({ boardId: this.boardId })._id; - // List where to insert card - this.listId = Blaze.getData(this.list[0])._id; - } - this.term = new ReactiveVar(''); + this.isListTemplateSearch = $(Popup._getTopStack().openerElement).hasClass( + 'js-list-template', + ); + this.isSwimlaneTemplateSearch = $( + Popup._getTopStack().openerElement, + ).hasClass('js-open-add-swimlane-menu'); + this.isBoardTemplateSearch = $(Popup._getTopStack().openerElement).hasClass( + 'js-add-board', + ); + this.isTemplateSearch = + this.isCardTemplateSearch || + this.isListTemplateSearch || + this.isSwimlaneTemplateSearch || + this.isBoardTemplateSearch; - this.getSortIndex = () => { - const position = Template.currentData().position; - let ret; - if (position === 'top') { - const firstCardDom = this.list.find('.js-minicard:first')[0]; - ret = Utils.calculateIndex(null, firstCardDom).base; - } else if (position === 'bottom') { - const lastCardDom = this.list.find('.js-minicard:last')[0]; - ret = Utils.calculateIndex(lastCardDom, null).base; + this.board = {}; + if (this.isTemplateSearch) { + const boardId = (ReactiveCache.getCurrentUser().profile || {}).templatesBoardId; + if (boardId) { + subManager.subscribe('board', boardId, false); + this.board = ReactiveCache.getBoard(boardId); + } + } else { + this.board = Utils.getCurrentBoard(); } - return ret; - }; -}); + if (!this.board) { + Popup.back(); + return; + } + this.boardId = this.board._id; + // Subscribe to this board + subManager.subscribe('board', this.boardId, false); + this.selectedBoardId = new ReactiveVar(this.boardId); + this.list = $(Popup._getTopStack().openerElement).closest('.js-list'); + + if (!this.isBoardTemplateSearch) { + this.swimlaneId = ''; + // Swimlane where to insert card + const swimlane = $(Popup._getTopStack().openerElement).parents( + '.js-swimlane', + ); + if (Utils.boardView() === 'board-view-swimlanes') + this.swimlaneId = Blaze.getData(swimlane[0])._id; + else this.swimlaneId = ReactiveCache.getSwimlane({ boardId: this.boardId })._id; + // List where to insert card + this.listId = Blaze.getData(this.list[0])._id; + } + this.term = new ReactiveVar(''); + }, -Template.searchElementPopup.helpers({ boards() { const ret = ReactiveCache.getBoards( { @@ -804,19 +710,18 @@ Template.searchElementPopup.helpers({ }, results() { - const tpl = Template.instance(); - if (!tpl.selectedBoardId) { + if (!this.selectedBoardId) { return []; } - const board = ReactiveCache.getBoard(tpl.selectedBoardId.get()); - if (!tpl.isTemplateSearch || tpl.isCardTemplateSearch) { - return board.searchCards(tpl.term.get(), false); - } else if (tpl.isListTemplateSearch) { - return board.searchLists(tpl.term.get()); - } else if (tpl.isSwimlaneTemplateSearch) { - return board.searchSwimlanes(tpl.term.get()); - } else if (tpl.isBoardTemplateSearch) { - const boards = board.searchBoards(tpl.term.get()); + const board = ReactiveCache.getBoard(this.selectedBoardId.get()); + if (!this.isTemplateSearch || this.isCardTemplateSearch) { + return board.searchCards(this.term.get(), false); + } else if (this.isListTemplateSearch) { + return board.searchLists(this.term.get()); + } else if (this.isSwimlaneTemplateSearch) { + return board.searchSwimlanes(this.term.get()); + } else if (this.isBoardTemplateSearch) { + const boards = board.searchBoards(this.term.get()); boards.forEach(board => { subManager.subscribe('board', board.linkedId, false); }); @@ -825,180 +730,184 @@ Template.searchElementPopup.helpers({ return []; } }, -}); -Template.searchElementPopup.events({ - 'change .js-select-boards'(evt, tpl) { - subManager.subscribe('board', $(evt.currentTarget).val(), false); - tpl.selectedBoardId.set($(evt.currentTarget).val()); - }, - 'submit .js-search-term-form'(evt, tpl) { - evt.preventDefault(); - tpl.term.set(evt.target.searchTerm.value); - }, - async 'click .js-minicard'(evt, tpl) { - // 0. Common - const title = $('.js-element-title') - .val() - .trim(); - if (!title) return; - const element = Blaze.getData(evt.currentTarget); - element.title = title; - let _id = ''; - if (!tpl.isTemplateSearch || tpl.isCardTemplateSearch) { - // Card insertion - // 1. Common - element.cardNumber = tpl.board.getNextCardNumber(); - element.sort = tpl.getSortIndex(); - // 1.A From template - if (tpl.isTemplateSearch) { - element.type = 'cardType-card'; - element.linkedId = ''; - _id = await element.copy(tpl.boardId, tpl.swimlaneId, tpl.listId); - // 1.B Linked card - } else { - _id = element.link(tpl.boardId, tpl.swimlaneId, tpl.listId); - } - Filter.addException(_id); - // List insertion - } else if (tpl.isListTemplateSearch) { - element.sort = ReactiveCache.getSwimlane(tpl.swimlaneId) - .lists() - .length; - element.type = 'list'; - _id = await element.copy(tpl.boardId, tpl.swimlaneId); - } else if (tpl.isSwimlaneTemplateSearch) { - element.sort = ReactiveCache.getBoard(tpl.boardId) - .swimlanes() - .length; - element.type = 'swimlane'; - _id = await element.copy(tpl.boardId); - } else if (tpl.isBoardTemplateSearch) { - Meteor.call( - 'copyBoard', - element.linkedId, - { - sort: ReactiveCache.getBoards({ archived: false }).length, - type: 'board', - title: element.title, - }, - (err, data) => { - _id = data; - subManager.subscribe('board', _id, false); - FlowRouter.go('board', { - id: _id, - slug: getSlug(element.title), - }); - }, - ); + getSortIndex() { + const position = this.data().position; + let ret; + if (position === 'top') { + const firstCardDom = this.list.find('.js-minicard:first')[0]; + ret = Utils.calculateIndex(null, firstCardDom).base; + } else if (position === 'bottom') { + const lastCardDom = this.list.find('.js-minicard:last')[0]; + ret = Utils.calculateIndex(lastCardDom, null).base; } - Popup.back(); + return ret; }, -}); -Template.spinnerList.helpers({ - getSpinnerTemplate() { - return getSpinnerTemplate(); + events() { + return [ + { + 'change .js-select-boards'(evt) { + subManager.subscribe('board', $(evt.currentTarget).val(), false); + this.selectedBoardId.set($(evt.currentTarget).val()); + }, + 'submit .js-search-term-form'(evt) { + evt.preventDefault(); + this.term.set(evt.target.searchTerm.value); + }, + 'click .js-minicard'(evt) { + // 0. Common + const title = $('.js-element-title') + .val() + .trim(); + if (!title) return; + const element = Blaze.getData(evt.currentTarget); + element.title = title; + let _id = ''; + if (!this.isTemplateSearch || this.isCardTemplateSearch) { + // Card insertion + // 1. Common + element.cardNumber = this.board.getNextCardNumber(); + element.sort = this.getSortIndex(); + // 1.A From template + if (this.isTemplateSearch) { + element.type = 'cardType-card'; + element.linkedId = ''; + _id = element.copy(this.boardId, this.swimlaneId, this.listId); + // 1.B Linked card + } else { + _id = element.link(this.boardId, this.swimlaneId, this.listId); + } + Filter.addException(_id); + // List insertion + } else if (this.isListTemplateSearch) { + element.sort = ReactiveCache.getSwimlane(this.swimlaneId) + .lists() + .length; + element.type = 'list'; + _id = element.copy(this.boardId, this.swimlaneId); + } else if (this.isSwimlaneTemplateSearch) { + element.sort = ReactiveCache.getBoard(this.boardId) + .swimlanes() + .length; + element.type = 'swimlane'; + _id = element.copy(this.boardId); + } else if (this.isBoardTemplateSearch) { + Meteor.call( + 'copyBoard', + element.linkedId, + { + sort: ReactiveCache.getBoards({ archived: false }).length, + type: 'board', + title: element.title, + }, + (err, data) => { + _id = data; + subManager.subscribe('board', _id, false); + FlowRouter.go('board', { + id: _id, + slug: getSlug(element.title), + }); + }, + ); + } + Popup.back(); + }, + }, + ]; }, - getSkSpinnerName() { - return 'sk-spinner-' + getSpinnerName().toLowerCase(); - }, -}); +}).register('searchElementPopup'); -Template.spinnerList.onCreated(function () { - this.swimlaneId = ''; -}); +(class extends Spinner { + onCreated() { + this.cardlimit = this.parentComponent().cardlimit; -Template.spinnerList.onRendered(function () { - const instance = this; + this.listId = this.parentComponent().data()._id; + this.swimlaneId = ''; - // Find the parent listBody template instance via the DOM - const listBodyEl = this.$('.sk-spinner-list').closest('.list-body')[0]; - const listBodyView = listBodyEl && Blaze.getView(listBodyEl, 'Template.listBody'); - const listBodyInstance = listBodyView?.templateInstance?.(); + const isSandstorm = + Meteor.settings && + Meteor.settings.public && + Meteor.settings.public.sandstorm; - instance.cardlimit = listBodyInstance && listBodyInstance.cardlimit; - instance.listId = listBodyInstance && Blaze.getData(listBodyEl)?._id; - - const isSandstorm = - Meteor.settings && - Meteor.settings.public && - Meteor.settings.public.sandstorm; - - // Get swimlane ID from the DOM hierarchy (listBody is inside list inside swimlane) - const getSwimlaneId = () => { - const swimlaneEl = listBodyEl && $(listBodyEl).closest('.swimlane').get(0); - return swimlaneEl && Blaze.getData(swimlaneEl)?._id; - }; - - if (isSandstorm) { - const user = ReactiveCache.getCurrentUser(); - if (user) { - if (Utils.boardView() === 'board-view-swimlanes') { - instance.swimlaneId = getSwimlaneId(); + if (isSandstorm) { + const user = ReactiveCache.getCurrentUser(); + if (user) { + if (Utils.boardView() === 'board-view-swimlanes') { + this.swimlaneId = this.parentComponent() + .parentComponent() + .parentComponent() + .data()._id; + } } + } else if (Utils.boardView() === 'board-view-swimlanes') { + this.swimlaneId = this.parentComponent() + .parentComponent() + .parentComponent() + .data()._id; } - } else if (Utils.boardView() === 'board-view-swimlanes') { - instance.swimlaneId = getSwimlaneId(); } - instance.spinner = instance.find('.sk-spinner-list'); - instance.container = listBodyEl; + onRendered() { + this.spinner = this.find('.sk-spinner-list'); + this.container = this.$(this.spinner).parents('.list-body')[0]; - $(instance.container).on( - `scroll.spinner_${instance.swimlaneId}_${instance.listId}`, - () => instance._updateList(), - ); - $(window).on(`resize.spinner_${instance.swimlaneId}_${instance.listId}`, () => - instance._updateList(), - ); + $(this.container).on( + `scroll.spinner_${this.swimlaneId}_${this.listId}`, + () => this.updateList(), + ); + $(window).on(`resize.spinner_${this.swimlaneId}_${this.listId}`, () => + this.updateList(), + ); - instance._updateList(); -}); + this.updateList(); + } -Template.spinnerList.onDestroyed(function () { - $(this.container).off(`scroll.spinner_${this.swimlaneId}_${this.listId}`); - $(window).off(`resize.spinner_${this.swimlaneId}_${this.listId}`); -}); + onDestroyed() { + $(this.container).off(`scroll.spinner_${this.swimlaneId}_${this.listId}`); + $(window).off(`resize.spinner_${this.swimlaneId}_${this.listId}`); + } -function checkIdleTime() { - return window.requestIdleCallback || - function (handler) { - const startTime = Date.now(); - return setTimeout(function () { - handler({ - didTimeout: false, - timeRemaining() { - return Math.max(0, 50.0 - (Date.now() - startTime)); - }, - }); - }, 1); - }; -} + checkIdleTime() { + return window.requestIdleCallback || + function (handler) { + const startTime = Date.now(); + return setTimeout(function () { + handler({ + didTimeout: false, + timeRemaining() { + return Math.max(0, 50.0 - (Date.now() - startTime)); + }, + }); + }, 1); + }; + } -Template.spinnerList.onCreated(function () { - const instance = this; - - instance._updateList = function () { + updateList() { // Use fallback when requestIdleCallback is not available on iOS and Safari // https://www.afasterweb.com/2017/11/20/utilizing-idle-moments/ - if (instance._spinnerInView()) { - instance.cardlimit.set(instance.cardlimit.get() + InfiniteScrollIter); - checkIdleTime()(() => instance._updateList()); - } - }; - instance._spinnerInView = function () { + if (this.spinnerInView()) { + this.cardlimit.set(this.cardlimit.get() + InfiniteScrollIter); + this.checkIdleTime(() => this.updateList()); + } + } + + spinnerInView() { // spinner deleted - if (!instance.spinner || !instance.spinner.offsetTop) { + if (!this.spinner.offsetTop) { return false; } - const spinnerViewPosition = instance.spinner.offsetTop - instance.container.offsetTop + instance.spinner.clientHeight; + const spinnerViewPosition = this.spinner.offsetTop - this.container.offsetTop + this.spinner.clientHeight; - const parentViewHeight = instance.container.clientHeight; - const bottomViewPosition = instance.container.scrollTop + parentViewHeight; + const parentViewHeight = this.container.clientHeight; + const bottomViewPosition = this.container.scrollTop + parentViewHeight; return bottomViewPosition > spinnerViewPosition; - }; -}); + } + + getSkSpinnerName() { + return "sk-spinner-" + super.getSpinnerName().toLowerCase(); + } +}.register('spinnerList')); diff --git a/client/components/lists/listHeader.jade b/client/components/lists/listHeader.jade index 967209374..df3c31f51 100644 --- a/client/components/lists/listHeader.jade +++ b/client/components/lists/listHeader.jade @@ -8,7 +8,7 @@ template(name="listHeader") if isMiniScreen if currentList a.list-header-left-icon.js-unselect-list - i.fa.fa-caret-left + | ◀️ else if collapsed if showCardsCountForList cards.length @@ -16,7 +16,7 @@ template(name="listHeader") span.cardCount {{cardsCount}} if isMiniScreen h2.list-header-name( - title="{{ displayDate modifiedAt 'LLL' }}" + title="{{ moment modifiedAt 'LLL' }}" class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}") +viewer = title @@ -26,19 +26,15 @@ template(name="listHeader") |/#{wipLimit.value}) if showCardsCountForList cards.length span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}} - if hasNumberFieldsSum - |   - span.list-sum-badge(title="{{_ 'sum-of-number-fields'}}") ∑ {{numberFieldsSum}} else - a.list-collapse-indicator.js-collapse(title="{{_ 'collapse'}}") - if collapsed - i.fa.fa-caret-right - else - i.fa.fa-caret-down + if collapsed + a.js-collapse(title="{{_ 'uncollapse'}}") + | ⬅️ + | ➡️ div(class="{{#if collapsed}}list-rotated{{/if}}") h2.list-header-name( - title="{{ displayDate modifiedAt 'LLL' }}" - class="{{#unless collapsed}}{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}{{/unless}}") + title="{{ moment modifiedAt 'LLL' }}" + class="{{#if currentUser.isBoardMember}}{{#unless currentUser.isCommentOnly}}{{#unless currentUser.isWorker}}js-open-inlined-form is-editable{{/unless}}{{/unless}}{{/if}}") +viewer = title if wipLimit.enabled @@ -48,54 +44,35 @@ template(name="listHeader") unless collapsed if showCardsCountForList cards.length span.cardCount {{cardsCount}} {{cardsCountForListIsOne cards.length}} - if hasNumberFieldsSum - |   - span.list-sum-badge(title="{{_ 'sum-of-number-fields'}}") ∑ {{numberFieldsSum}} if isMiniScreen if currentList if isWatching - i.list-header-watch-icon i.fa.fa-eye + i.list-header-watch-icon | 👁️ div.list-header-menu unless currentUser.isCommentOnly - unless currentUser.isReadOnly - unless currentUser.isReadAssignedOnly - if canSeeAddCard - a.js-add-card.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}") - i.fa.fa-plus - a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") - i.fa.fa-bars + if canSeeAddCard + a.js-add-card.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}") ➕ + a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") ☰ else - a.list-header-menu-icon.js-select-list - i.fa.fa-caret-right - unless currentUser.isWorker - if isTouchScreenOrShowDesktopDragHandles - a.list-header-handle.handle.js-list-handle - i.fa.fa-arrows + a.list-header-menu-icon.js-select-list ▶️ + a.list-header-handle.handle.js-list-handle ↕️ else if currentUser.isBoardMember if isWatching - i.list-header-watch-icon i.fa.fa-eye - unless currentUser.isCommentOnly - unless currentUser.isReadOnly - unless currentUser.isReadAssignedOnly - if isTouchScreenOrShowDesktopDragHandles - a.list-header-handle-desktop.handle.js-list-handle(title="{{_ 'drag-list'}}") - i.fa.fa-arrows + i.list-header-watch-icon | 👁️ unless collapsed div.list-header-menu unless currentUser.isCommentOnly - unless currentUser.isReadOnly - unless currentUser.isReadAssignedOnly - //if isBoardAdmin - // - a.fa.js-list-star.list-header-plus-top(class="fa-star{{#unless starred}}-o{{/unless}}") - if isTouchScreenOrShowDesktopDragHandles - a.list-header-handle-desktop.handle.js-list-handle(title="{{_ 'drag-list'}}") - i.fa.fa-arrows - if canSeeAddCard - a.js-add-card.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}") - i.fa.fa-plus - a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") - i.fa.fa-bars + //if isBoardAdmin + // a.fa.js-list-star.list-header-plus-top(class="fa-star{{#unless starred}}-o{{/unless}}") + if canSeeAddCard + a.js-add-card.list-header-plus-top(title="{{_ 'add-card-to-top-of-list'}}") ➕ + a.js-collapse(title="{{_ 'collapse'}}") + | ⬅️ + | ➡️ + a.js-open-list-menu(title="{{_ 'listActionPopup-title'}}") ☰ + if currentUser.isBoardMember + unless currentUser.isCommentOnly + a.list-header-handle.handle.js-list-handle ↕️ template(name="editListTitleForm") .list-composer @@ -103,78 +80,63 @@ template(name="editListTitleForm") .edit-controls.clearfix button.primary.confirm(type="submit") {{_ 'save'}} a.js-close-inlined-form - i.fa.fa-times-thin + | ❌ template(name="listActionPopup") - unless currentUser.isReadOnly - unless currentUser.isReadAssignedOnly - ul.pop-over-list - li - a.js-add-card.list-header-plus-top - i.fa.fa-plus - i.fa.fa-arrow-up - | {{_ 'add-card-to-top-of-list'}} - li - a.js-add-card.list-header-plus-bottom - i.fa.fa-plus - i.fa.fa-arrow-down - | {{_ 'add-card-to-bottom-of-list'}} - hr - ul.pop-over-list - li - a.js-add-list - i.fa.fa-plus - | {{_ 'add-list'}} - hr - ul.pop-over-list - li - a.js-set-list-width - i.fa.fa-arrows-h - | {{_ 'set-list-width'}} + ul.pop-over-list + li + a.js-add-card.list-header-plus-bottom + | ➕ + | ⬇️ + | {{_ 'add-card-to-bottom-of-list'}} + hr + ul.pop-over-list + li + a.js-set-list-width + | ↔️ + | {{_ 'set-list-width'}} ul.pop-over-list li a.js-toggle-watch-list if isWatching - i.fa.fa-eye + | 👁️ | {{_ 'unwatch'}} else - i.fa.fa-eye-slash + | 🙈 | {{_ 'watch'}} unless currentUser.isCommentOnly - unless currentUser.isReadOnly - unless currentUser.isReadAssignedOnly - unless currentUser.isWorker - ul.pop-over-list - li - a.js-set-color-list - i.fa.fa-paint-brush - | {{_ 'set-color-list'}} - ul.pop-over-list - if cards.length - li - a.js-select-cards - i.fa.fa-select-square - | {{_ 'list-select-cards'}} - if currentUser.isBoardAdmin - ul.pop-over-list - li - a.js-set-wip-limit - i.fa.fa-ban - | {{#if isWipLimitEnabled }}{{_ 'edit-wip-limit'}}{{else}}{{_ 'setWipLimitPopup-title'}}{{/if}} - unless currentUser.isWorker - hr - ul.pop-over-list - li - a.js-close-list - i.fa.fa-arrow-right - i.fa.fa-archive - | {{_ 'archive-list'}} - hr - ul.pop-over-list - li - a.js-more - i.fa.fa-link - | {{_ 'listMorePopup-title'}} + unless currentUser.isWorker + ul.pop-over-list + li + a.js-set-color-list + | 🎨 + | {{_ 'set-color-list'}} + ul.pop-over-list + if cards.length + li + a.js-select-cards + | ☑️ + | {{_ 'list-select-cards'}} + if currentUser.isBoardAdmin + ul.pop-over-list + li + a.js-set-wip-limit + | 🚫 + | {{#if isWipLimitEnabled }}{{_ 'edit-wip-limit'}}{{else}}{{_ 'setWipLimitPopup-title'}}{{/if}} + unless currentUser.isWorker + hr + ul.pop-over-list + li + a.js-close-list + | ➡️ + | 📦 + | {{_ 'archive-list'}} + hr + ul.pop-over-list + li + a.js-more + | 🔗 + | {{_ 'listMorePopup-title'}} template(name="boardLists") ul.pop-over-list @@ -190,15 +152,13 @@ template(name="listMorePopup") span.clearfix span {{_ 'link-list'}} = ' ' - i.fa(class="{{#if currentBoard.isPublic}}fa-globe{{else}}fa-lock{{/if}}") + | {{#if board.isPublic}}🌐{{else}}🔒{{/if}} input.inline-input(type="text" readonly value="{{ rootUrl }}") | {{_ 'added'}} - span.date(title=list.createdAt) {{ displayDate createdAt 'LLL' }} + span.date(title=list.createdAt) {{ moment createdAt 'LLL' }} //unless currentUser.isWorker - // - if currentUser.isBoardAdmin - // - a.js-delete {{_ 'delete'}} + // if currentUser.isBoardAdmin + // a.js-delete {{_ 'delete'}} template(name="listDeletePopup") p {{_ "list-delete-pop"}} @@ -212,7 +172,7 @@ template(name="setWipLimitPopup") ul.pop-over-list li: a.js-enable-wip-limit {{_ 'enable-wip-limit'}} if isWipLimitEnabled - i.fa.fa-check + | ✅ if isWipLimitEnabled p input.wip-limit-value(type="number" value="{{ wipLimitValue }}" min="1" max="99") @@ -233,8 +193,8 @@ template(name="setListWidthPopup") #js-list-width-edit label {{_ 'set-list-width-value'}} p - input.list-width-value(type="number" value="{{ listWidthValue }}" min="270") - input.list-constraint-value(type="number" value="{{ listConstraintValue }}" min="270") + input.list-width-value(type="number" value="{{ listWidthValue }}" min="100") + input.list-constraint-value(type="number" value="{{ listConstraintValue }}" min="100") input.list-width-apply(type="submit" value="{{_ 'apply'}}") input.list-width-error br @@ -245,7 +205,7 @@ template(name="setListWidthPopup") template(name="listWidthErrorPopup") .list-width-invalid - p {{_ 'list-width-error-message'}} '>=270' + p {{_ 'list-width-error-message'}} '>=100' button.full.js-back-view(type="submit") {{_ 'cancel'}} template(name="setListColorPopup") @@ -254,29 +214,6 @@ template(name="setListColorPopup") // note: we use the swimlane palette to have more than just the border span.card-label.palette-color.js-palette-color(class="card-details-{{color}}") if(isSelected color) - i.fa.fa-check + | ✅ button.primary.confirm.js-submit {{_ 'save'}} button.js-remove-color.negate.wide.right {{_ 'unset-color'}} - -template(name="addListPopup") - form.js-add-list-form - input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}" autocomplete="off" autofocus) - if currentSwimlaneData - if swimlaneLists.length - label {{_ 'add-after-list'}} - select.list-position-input.full-line - each swimlaneLists - option(value="{{_id}}" selected="{{$eq _id currentListIdValue}}") {{increment @index}} {{title}} - else - if currentBoard.lists.length - label {{_ 'add-after-list'}} - select.list-position-input.full-line - each currentBoard.lists - option(value="{{_id}}" selected="{{$eq _id currentListIdValue}}") {{increment @index}} {{title}} - .edit-controls.clearfix - button.primary.confirm.js-submit-add-list(type="submit") {{_ 'save'}} - unless currentBoard.isTemplatesBoard - unless currentBoard.isTemplateBoard - span.quiet - | {{_ 'or'}} - a.js-list-template {{_ 'template'}} diff --git a/client/components/lists/listHeader.js b/client/components/lists/listHeader.js index 457ea429b..5a3e212d8 100644 --- a/client/components/lists/listHeader.js +++ b/client/components/lists/listHeader.js @@ -1,5 +1,4 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import Lists from '../../../models/lists'; import { TAPi18n } from '/imports/i18n'; import dragscroll from '@wekanteam/dragscroll'; @@ -8,13 +7,13 @@ Meteor.startup(() => { listsColors = Lists.simpleSchema()._schema.color.allowedValues; }); -Template.listHeader.helpers({ +BlazeComponent.extendComponent({ canSeeAddCard() { const list = Template.currentData(); return ( (!list.getWipLimit('enabled') || list.getWipLimit('soft') || - !Template.instance().reachedWipLimit()) && + !this.reachedWipLimit()) && !ReactiveCache.getCurrentUser().isWorker() ); }, @@ -22,19 +21,41 @@ Template.listHeader.helpers({ isBoardAdmin() { return ReactiveCache.getCurrentUser().isBoardAdmin(); }, - - starred() { + starred(check = undefined) { const list = Template.currentData(); - return list.isStarred(); + const status = list.isStarred(); + if (check === undefined) { + // just check + return status; + } else { + list.star(!status); + return !status; + } }, - - collapsed() { + collapsed(check = undefined) { const list = Template.currentData(); - return Utils.getListCollapseState(list); + const status = list.isCollapsed(); + if (check === undefined) { + // just check + return status; + } else { + list.collapse(!status); + return !status; + } + }, + editTitle(event) { + event.preventDefault(); + const newTitle = this.childComponents('inlinedForm')[0] + .getValue() + .trim(); + const list = this.currentData(); + if (newTitle) { + list.rename(newTitle.trim()); + } }, isWatching() { - const list = Template.currentData(); + const list = this.currentData(); return list.findWatcher(Meteor.userId()); }, @@ -50,9 +71,10 @@ Template.listHeader.helpers({ cardsCount() { const list = Template.currentData(); let swimlaneId = ''; - if (Utils.boardView() === 'board-view-swimlanes') { - swimlaneId = list.swimlaneId || ''; - } + if (Utils.boardView() === 'board-view-swimlanes') + swimlaneId = this.parentComponent() + .parentComponent() + .data()._id; const ret = list.cards(swimlaneId).length; return ret; @@ -75,8 +97,7 @@ Template.listHeader.helpers({ }, showCardsCountForList(count) { - const currentUser = ReactiveCache.getCurrentUser(); - const limit = currentUser ? currentUser.getLimitToShowCardsCount() : false; + const limit = this.limitToShowCardsCount(); return limit >= 0 && count >= limit; }, @@ -88,98 +109,40 @@ Template.listHeader.helpers({ } }, - numberFieldsSum() { - const list = Template.currentData(); - if (!list) return 0; - const boardId = Session.get('currentBoard'); - const fields = ReactiveCache.getCustomFields({ - boardIds: { $in: [boardId] }, - showSumAtTopOfList: true, - type: 'number', - }); - if (!fields || !fields.length) return 0; - const cards = ReactiveCache.getCards({ listId: list._id, archived: false }); - let total = 0; - if (cards && cards.length) { - cards.forEach(card => { - const cfs = (card.customFields || []); - fields.forEach(field => { - const cf = cfs.find(f => f && f._id === field._id); - if (!cf || cf.value === null || cf.value === undefined) return; - let v = cf.value; - if (typeof v === 'string') { - const parsed = parseFloat(v.replace(',', '.')); - if (isNaN(parsed)) return; - v = parsed; - } - if (typeof v === 'number' && isFinite(v)) { - total += v; - } - }); - }); - } - return total; + events() { + return [ + { + 'click .js-list-star'(event) { + event.preventDefault(); + this.starred(!this.starred()); + }, + 'click .js-collapse'(event) { + event.preventDefault(); + this.collapsed(!this.collapsed()); + }, + 'click .js-open-list-menu': Popup.open('listAction'), + 'click .js-add-card.list-header-plus-top'(event) { + const listDom = $(event.target).parents( + `#js-list-${this.currentData()._id}`, + )[0]; + const listComponent = BlazeComponent.getComponentForElement(listDom); + listComponent.openForm({ + position: 'top', + }); + }, + 'click .js-unselect-list'() { + Session.set('currentList', null); + }, + submit: this.editTitle, + }, + ]; }, +}).register('listHeader'); - hasNumberFieldsSum() { - const boardId = Session.get('currentBoard'); - const fields = ReactiveCache.getCustomFields({ - boardIds: { $in: [boardId] }, - showSumAtTopOfList: true, - type: 'number', - }); - return !!(fields && fields.length); - }, -}); - -// Helper function on template instance for reachedWipLimit check -Template.listHeader.onCreated(function () { - this.reachedWipLimit = function () { - const list = Template.currentData(); - return ( - list.getWipLimit('enabled') && - list.getWipLimit('value') <= list.cards().length - ); - }; -}); - -Template.listHeader.events({ - async 'click .js-list-star'(event) { - event.preventDefault(); - const list = Template.currentData(); - const status = list.isStarred(); - await list.star(!status); - }, - 'click .js-collapse'(event) { - event.preventDefault(); - const list = Template.currentData(); - const status = Utils.getListCollapseState(list); - Utils.setListCollapseState(list, !status); - }, - 'click .js-open-list-menu': Popup.open('listAction'), - 'click .js-add-card.list-header-plus-top'(event) { - const listDom = $(event.target).parents( - `#js-list-${Template.currentData()._id}`, - )[0]; - const view = Blaze.getView(listDom, 'Template.listBody'); - const listComponent = view?.templateInstance?.(); - if (listComponent) { - listComponent.openForm({ - position: 'top', - }); - } - }, - 'click .js-unselect-list'() { - Session.set('currentList', null); - }, - async 'submit'(event, tpl) { - event.preventDefault(); - const newTitle = tpl.$('textarea,input[type=text]').val()?.trim(); - const list = Template.currentData(); - if (newTitle) { - await list.rename(newTitle.trim()); - } - }, +Template.listHeader.helpers({ + isBoardAdmin() { + return ReactiveCache.getCurrentUser().isBoardAdmin(); + } }); Template.listActionPopup.helpers({ @@ -198,29 +161,14 @@ Template.listActionPopup.helpers({ Template.listActionPopup.events({ 'click .js-list-subscribe'() {}, - 'click .js-add-card.list-header-plus-top'(event) { - const listDom = $(`#js-list-${this._id}`)[0]; - const view = Blaze.getView(listDom, 'Template.listBody'); - const listComponent = view?.templateInstance?.(); - if (listComponent) { - listComponent.openForm({ - position: 'top', - }); - } - Popup.back(); - }, 'click .js-add-card.list-header-plus-bottom'(event) { const listDom = $(`#js-list-${this._id}`)[0]; - const view = Blaze.getView(listDom, 'Template.listBody'); - const listComponent = view?.templateInstance?.(); - if (listComponent) { - listComponent.openForm({ - position: 'bottom', - }); - } + const listComponent = BlazeComponent.getComponentForElement(listDom); + listComponent.openForm({ + position: 'bottom', + }); Popup.back(); }, - 'click .js-add-list': Popup.open('addList'), 'click .js-set-list-width': Popup.open('setListWidth'), 'click .js-set-color-list': Popup.open('setListColor'), 'click .js-select-cards'() { @@ -235,16 +183,59 @@ Template.listActionPopup.events({ if (!err && ret) Popup.back(); }); }, - async 'click .js-close-list'(event) { + 'click .js-close-list'(event) { event.preventDefault(); - await this.archive(); + this.archive(); Popup.back(); }, 'click .js-set-wip-limit': Popup.open('setWipLimit'), 'click .js-more': Popup.open('listMore'), }); -Template.setWipLimitPopup.helpers({ +BlazeComponent.extendComponent({ + applyWipLimit() { + const list = Template.currentData(); + const limit = parseInt( + Template.instance() + .$('.wip-limit-value') + .val(), + 10, + ); + + if (limit < list.cards().length && !list.getWipLimit('soft')) { + Template.instance() + .$('.wip-limit-error') + .click(); + } else { + Meteor.call('applyWipLimit', list._id, limit); + Popup.back(); + } + }, + + enableSoftLimit() { + const list = Template.currentData(); + + if ( + list.getWipLimit('soft') && + list.getWipLimit('value') < list.cards().length + ) { + list.setWipLimit(list.cards().length); + } + Meteor.call('enableSoftLimit', Template.currentData()._id); + }, + + enableWipLimit() { + const list = Template.currentData(); + // Prevent user from using previously stored wipLimit.value if it is less than the current number of cards in the list + if ( + !list.getWipLimit('enabled') && + list.getWipLimit('value') < list.cards().length + ) { + list.setWipLimit(list.cards().length); + } + Meteor.call('enableWipLimit', list._id); + }, + isWipLimitSoft() { return Template.currentData().getWipLimit('soft'); }, @@ -256,114 +247,125 @@ Template.setWipLimitPopup.helpers({ wipLimitValue() { return Template.currentData().getWipLimit('value'); }, -}); -Template.setWipLimitPopup.events({ - async 'click .js-enable-wip-limit'() { - const list = Template.currentData(); - // Prevent user from using previously stored wipLimit.value if it is less than the current number of cards in the list - if ( - !list.getWipLimit('enabled') && - list.getWipLimit('value') < list.cards().length - ) { - await list.setWipLimit(list.cards().length); - } - Meteor.call('enableWipLimit', list._id); + events() { + return [ + { + 'click .js-enable-wip-limit': this.enableWipLimit, + 'click .wip-limit-apply': this.applyWipLimit, + 'click .wip-limit-error': Popup.open('wipLimitError'), + 'click .materialCheckBox': this.enableSoftLimit, + }, + ]; }, - 'click .wip-limit-apply'(event, tpl) { - const list = Template.currentData(); - const limit = parseInt( - tpl.$('.wip-limit-value').val(), - 10, - ); - - if (limit < list.cards().length && !list.getWipLimit('soft')) { - tpl.$('.wip-limit-error').click(); - } else { - Meteor.call('applyWipLimit', list._id, limit); - Popup.back(); - } - }, - 'click .wip-limit-error': Popup.open('wipLimitError'), - async 'click .materialCheckBox'() { - const list = Template.currentData(); - - if ( - list.getWipLimit('soft') && - list.getWipLimit('value') < list.cards().length - ) { - await list.setWipLimit(list.cards().length); - } - Meteor.call('enableSoftLimit', Template.currentData()._id); - }, -}); +}).register('setWipLimitPopup'); Template.listMorePopup.events({ 'click .js-delete': Popup.afterConfirm('listDelete', function() { Popup.back(); - const list = Lists.findOne(this._id); - if (!list) return; - const allCards = list.allCards(); + const allCards = this.allCards(); const allCardIds = _.pluck(allCards, '_id'); // it's okay if the linked cards are on the same list if ( ReactiveCache.getCards({ $and: [ - { listId: { $ne: list._id } }, + { listId: { $ne: this._id } }, { linkedId: { $in: allCardIds } }, ], }).length === 0 ) { allCardIds.map(_id => Cards.remove(_id)); - Lists.remove(list._id); + Lists.remove(this._id); } else { + // TODO: Figure out more informative message. + // Popup with a hint that the list cannot be deleted as there are + // linked cards. We can adapt the query above so we can list the linked + // cards. + // Related: + // client/components/cards/cardDetails.js about line 969 + // https://github.com/wekan/wekan/issues/2785 const message = `${TAPi18n.__( 'delete-linked-cards-before-this-list', )} linkedId: ${ - list._id + this._id } at client/components/lists/listHeader.js and https://github.com/wekan/wekan/issues/2785`; alert(message); } - Utils.goBoardId(list.boardId); + Utils.goBoardId(this.boardId); }), }); -Template.setListColorPopup.onCreated(function () { - const data = Template.currentData(); - this.currentList = Lists.findOne(data._id) || data; - this.currentColor = new ReactiveVar(this.currentList.color); +Template.listHeader.helpers({ + isBoardAdmin() { + return ReactiveCache.getCurrentUser().isBoardAdmin(); + }, }); -Template.setListColorPopup.helpers({ +BlazeComponent.extendComponent({ + onCreated() { + this.currentList = this.currentData(); + this.currentColor = new ReactiveVar(this.currentList.color); + }, + colors() { return listsColors.map(color => ({ color, name: '' })); }, isSelected(color) { - const tpl = Template.instance(); - if (tpl.currentColor.get() === null) { + if (this.currentColor.get() === null) { return color === 'white'; } else { - return tpl.currentColor.get() === color; + return this.currentColor.get() === color; } }, -}); -Template.setListColorPopup.events({ - 'click .js-palette-color'(event, tpl) { - tpl.currentColor.set(Template.currentData().color); + events() { + return [ + { + 'click .js-palette-color'() { + this.currentColor.set(this.currentData().color); + }, + 'click .js-submit'() { + this.currentList.setColor(this.currentColor.get()); + Popup.close(); + }, + 'click .js-remove-color'() { + this.currentList.setColor(null); + Popup.close(); + }, + }, + ]; + }, +}).register('setListColorPopup'); + +BlazeComponent.extendComponent({ + applyListWidth() { + const list = Template.currentData(); + const board = list.boardId; + const width = parseInt( + Template.instance() + .$('.list-width-value') + .val(), + 10, + ); + const constraint = parseInt( + Template.instance() + .$('.list-constraint-value') + .val(), + 10, + ); + + // FIXME(mark-i-m): where do we put constants? + if (width < 100 || !width || constraint < 100 || !constraint) { + Template.instance() + .$('.list-width-error') + .click(); + } else { + Meteor.call('applyListWidth', board, list._id, width, constraint); + Popup.back(); + } }, - async 'click .js-submit'(event, tpl) { - await tpl.currentList.setColor(tpl.currentColor.get()); - Popup.close(); - }, - async 'click .js-remove-color'(event, tpl) { - await tpl.currentList.setColor(null); - Popup.close(); - }, -}); -Template.setListWidthPopup.helpers({ listWidthValue() { const list = Template.currentData(); const board = list.boardId; @@ -381,147 +383,17 @@ Template.setListWidthPopup.helpers({ const user = ReactiveCache.getCurrentUser(); return user && user.isAutoWidth(boardId); }, -}); -Template.setListWidthPopup.events({ - 'click .js-auto-width-board'() { - dragscroll.reset(); - ReactiveCache.getCurrentUser().toggleAutoWidth(Utils.getCurrentBoardId()); + events() { + return [ + { + 'click .js-auto-width-board'() { + dragscroll.reset(); + ReactiveCache.getCurrentUser().toggleAutoWidth(Utils.getCurrentBoardId()); + }, + 'click .list-width-apply': this.applyListWidth, + 'click .list-width-error': Popup.open('listWidthError'), + }, + ]; }, - 'click .list-width-apply'(event, tpl) { - const list = Template.currentData(); - const board = list.boardId; - const width = parseInt( - tpl.$('.list-width-value').val(), - 10, - ); - const constraint = parseInt( - tpl.$('.list-constraint-value').val(), - 10, - ); - - // FIXME(mark-i-m): where do we put constants? - if (width < 270 || !width || constraint < 270 || !constraint) { - tpl.$('.list-width-error').click(); - } else { - Meteor.call('applyListWidth', board, list._id, width, constraint); - Popup.back(); - } - }, - 'click .list-width-error': Popup.open('listWidthError'), -}); - -Template.addListPopup.onCreated(function () { - this.currentBoard = Utils.getCurrentBoard(); - this.currentSwimlaneId = new ReactiveVar(null); - this.currentListId = new ReactiveVar(null); - - // Get the swimlane context from opener - const openerComponent = Popup.getOpenerComponent(); - const openerData = openerComponent?.data || Popup._getTopStack()?.dataContext; - - // If opened from swimlane menu, openerData is the swimlane - if (openerData?.type === 'swimlane' || openerData?.type === 'template-swimlane') { - this.currentSwimlane = openerData; - this.currentSwimlaneId.set(openerData._id); - } else if (openerData?._id) { - // If opened from list menu, get swimlane from the list - const list = ReactiveCache.getList({ _id: openerData._id }); - if (list) { - this.currentSwimlane = list.swimlaneId - ? ReactiveCache.getSwimlane({ _id: list.swimlaneId }) - : null; - this.currentSwimlaneId.set(this.currentSwimlane?._id || null); - this.currentListId.set(openerData._id); - } - } - - if (!this.currentSwimlaneId.get()) { - const defaultSwimlane = this.currentBoard.getDefaultSwimline?.(); - if (defaultSwimlane?._id) { - this.currentSwimlane = defaultSwimlane; - this.currentSwimlaneId.set(defaultSwimlane._id); - } - } -}); - -Template.addListPopup.helpers({ - currentSwimlaneData() { - const tpl = Template.instance(); - const swimlaneId = tpl.currentSwimlaneId.get(); - return swimlaneId ? ReactiveCache.getSwimlane({ _id: swimlaneId }) : null; - }, - - currentListIdValue() { - return Template.instance().currentListId.get(); - }, - - swimlaneLists() { - const tpl = Template.instance(); - const swimlaneId = tpl.currentSwimlaneId.get(); - if (swimlaneId) { - return ReactiveCache.getLists({ swimlaneId, archived: false }).sort((a, b) => a.sort - b.sort); - } - return tpl.currentBoard.lists; - }, -}); - -Template.addListPopup.events({ - 'submit .js-add-list-form'(evt, tpl) { - evt.preventDefault(); - - const titleInput = tpl.find('.list-name-input'); - const title = titleInput?.value.trim(); - - if (!title) return; - - let sortIndex = 0; - const boardId = Utils.getCurrentBoardId(); - let swimlaneId = tpl.currentSwimlane?._id; - - const positionInput = tpl.find('.list-position-input'); - - if (positionInput && positionInput.value) { - const positionId = positionInput.value.trim(); - const selectedList = ReactiveCache.getList({ boardId, _id: positionId, archived: false }); - - if (selectedList) { - sortIndex = selectedList.sort + 1; - // Use the swimlane ID from the selected list to ensure the new list - // is added to the same swimlane as the selected list - swimlaneId = selectedList.swimlaneId; - } else { - // No specific position, add at end of swimlane - if (swimlaneId) { - const swimlaneLists = ReactiveCache.getLists({ swimlaneId, archived: false }); - const lastSwimlaneList = swimlaneLists.sort((a, b) => b.sort - a.sort)[0]; - sortIndex = Utils.calculateIndexData(lastSwimlaneList, null).base; - } else { - const lastList = tpl.currentBoard.getLastList(); - sortIndex = Utils.calculateIndexData(lastList, null).base; - } - } - } else { - // No position input, add at end of swimlane - if (swimlaneId) { - const swimlaneLists = ReactiveCache.getLists({ swimlaneId, archived: false }); - const lastSwimlaneList = swimlaneLists.sort((a, b) => b.sort - a.sort)[0]; - sortIndex = Utils.calculateIndexData(lastSwimlaneList, null).base; - } else { - const lastList = tpl.currentBoard.getLastList(); - sortIndex = Utils.calculateIndexData(lastList, null).base; - } - } - - Lists.insert({ - title, - boardId: Session.get('currentBoard'), - sort: sortIndex, - type: 'list', - swimlaneId: swimlaneId, - }); - - Popup.back(); - }, - 'click .js-list-template': Popup.open('searchElement'), -}); +}).register('setListWidthPopup'); diff --git a/client/components/lists/minilist.jade b/client/components/lists/minilist.jade index 6603e0bdf..e34214c40 100644 --- a/client/components/lists/minilist.jade +++ b/client/components/lists/minilist.jade @@ -3,7 +3,6 @@ template(name="minilist") class="minicard-{{colorClass}}") .minicard-title .handle - span.drag-handle(title="{{_ 'dragList'}}") - i.fa.fa-arrows + .fa.fa-arrows +viewer = title diff --git a/client/components/main/accessibility.js b/client/components/main/accessibility.js index 094961067..b6204dc4b 100644 --- a/client/components/main/accessibility.js +++ b/client/components/main/accessibility.js @@ -18,19 +18,21 @@ const accessibilityHelpers = { }; // Main accessibility page component -Template.accessibility.onCreated(function () { - this.error = new ReactiveVar(''); - this.loading = new ReactiveVar(false); +BlazeComponent.extendComponent({ + onCreated() { + this.error = new ReactiveVar(''); + this.loading = new ReactiveVar(false); - Meteor.subscribe('setting'); - Meteor.subscribe('accessibilitySettings'); -}); - -Template.accessibility.helpers(accessibilityHelpers); + Meteor.subscribe('setting'); + Meteor.subscribe('accessibilitySettings'); + }, + ...accessibilityHelpers +}).register('accessibility'); // Header bar component -Template.accessibilityHeaderBar.onCreated(function () { - Meteor.subscribe('accessibilitySettings'); -}); - -Template.accessibilityHeaderBar.helpers(accessibilityHelpers); +BlazeComponent.extendComponent({ + onCreated() { + Meteor.subscribe('accessibilitySettings'); + }, + ...accessibilityHelpers +}).register('accessibilityHeaderBar'); diff --git a/client/components/main/bookmarks.js b/client/components/main/bookmarks.js index 2e0504960..6ec110593 100644 --- a/client/components/main/bookmarks.js +++ b/client/components/main/bookmarks.js @@ -15,12 +15,12 @@ Template.bookmarks.helpers({ }); Template.bookmarks.events({ - async 'click .js-toggle-star'(e) { + 'click .js-toggle-star'(e) { e.preventDefault(); const boardId = this._id; const user = ReactiveCache.getCurrentUser(); if (user && boardId) { - await user.toggleBoardStar(boardId); + user.toggleBoardStar(boardId); } }, }); @@ -42,12 +42,12 @@ Template.bookmarksPopup.helpers({ }); Template.bookmarksPopup.events({ - async 'click .js-toggle-star'(e) { + 'click .js-toggle-star'(e) { e.preventDefault(); const boardId = this._id; const user = ReactiveCache.getCurrentUser(); if (user && boardId) { - await user.toggleBoardStar(boardId); + user.toggleBoardStar(boardId); } }, }); diff --git a/client/components/main/brokenCards.js b/client/components/main/brokenCards.js index 0c4fd3bf8..17d30f37c 100644 --- a/client/components/main/brokenCards.js +++ b/client/components/main/brokenCards.js @@ -1,57 +1,18 @@ -import { CardSearchPaged } from '../../lib/cardSearch'; +import { CardSearchPagedComponent } from '../../lib/cardSearch'; -Template.brokenCards.onCreated(function () { - const search = new CardSearchPaged(this); - this.search = search; - - Meteor.subscribe('brokenCards', search.sessionId); -}); +BlazeComponent.extendComponent({}).register('brokenCardsHeaderBar'); Template.brokenCards.helpers({ userId() { return Meteor.userId(); }, - - // Return ReactiveVars so jade can use .get pattern - searching() { - return Template.instance().search.searching; - }, - hasResults() { - return Template.instance().search.hasResults; - }, - hasQueryErrors() { - return Template.instance().search.hasQueryErrors; - }, - errorMessages() { - return Template.instance().search.queryErrorMessages(); - }, - resultsCount() { - return Template.instance().search.resultsCount; - }, - resultsHeading() { - return Template.instance().search.resultsHeading; - }, - results() { - return Template.instance().search.results; - }, - getSearchHref() { - return Template.instance().search.getSearchHref(); - }, - hasPreviousPage() { - return Template.instance().search.hasPreviousPage; - }, - hasNextPage() { - return Template.instance().search.hasNextPage; - }, }); -Template.brokenCards.events({ - 'click .js-next-page'(evt, tpl) { - evt.preventDefault(); - tpl.search.nextPage(); - }, - 'click .js-previous-page'(evt, tpl) { - evt.preventDefault(); - tpl.search.previousPage(); - }, -}); +class BrokenCardsComponent extends CardSearchPagedComponent { + onCreated() { + super.onCreated(); + + Meteor.subscribe('brokenCards', this.sessionId); + } +} +BrokenCardsComponent.register('brokenCards'); diff --git a/client/components/main/dueCards.jade b/client/components/main/dueCards.jade index 8a972c441..f482c9233 100644 --- a/client/components/main/dueCards.jade +++ b/client/components/main/dueCards.jade @@ -1,23 +1,23 @@ template(name="dueCardsHeaderBar") if currentUser h1 - i.fa.fa-calendar + | 📅 | {{_ 'dueCards-title'}} .board-header-btns.left a.board-header-btn.js-due-cards-view-change(title="{{_ 'dueCardsViewChange-title'}}") - i.fa.fa-caret-down + | ▼ if $eq dueCardsView 'me' - i.fa.fa-user + | 👤 | {{_ 'dueCardsViewChange-choice-me'}} if $eq dueCardsView 'all' - i.fa.fa-users + | 👥 | {{_ 'dueCardsViewChange-choice-all'}} template(name="dueCardsModalTitle") if currentUser h2 - i.fa.fa-keyboard-o + | ⌨️ | {{_ 'dueCards-title'}} template(name="dueCards") @@ -49,18 +49,18 @@ template(name="dueCardsViewChangePopup") li with "dueCardsViewChange-choice-me" a.js-due-cards-view-me - i.fa.fa-user + | 👤 | {{_ 'dueCardsViewChange-choice-me'}} if $eq Utils.dueCardsView "me" - i.fa.fa-check + | ✅ hr li with "dueCardsViewChange-choice-all" a.js-due-cards-view-all - i.fa.fa-users + | 👥 | {{_ 'dueCardsViewChange-choice-all'}} span.sub-name +viewer | {{_ 'dueCardsViewChange-choice-all-description' }} if $eq Utils.dueCardsView "all" - i.fa.fa-check + | ✅ diff --git a/client/components/main/dueCards.js b/client/components/main/dueCards.js index 6d8d8526c..f17bc9a74 100644 --- a/client/components/main/dueCards.js +++ b/client/components/main/dueCards.js @@ -1,37 +1,178 @@ import { ReactiveCache } from '/imports/reactiveCache'; +import { BlazeComponent } from 'meteor/peerlibrary:blaze-components'; import { TAPi18n } from '/imports/i18n'; // const subManager = new SubsManager(); -Template.dueCardsHeaderBar.helpers({ +BlazeComponent.extendComponent({ dueCardsView() { // eslint-disable-next-line no-console // console.log('sort:', Utils.dueCardsView()); return Utils && Utils.dueCardsView ? Utils.dueCardsView() : 'me'; }, + + events() { + return [ + { + 'click .js-due-cards-view-change': Popup.open('dueCardsViewChange'), + }, + ]; + }, +}).register('dueCardsHeaderBar'); + +Template.dueCards.helpers({ + userId() { + return Meteor.userId(); + }, + dueCardsList() { + const component = BlazeComponent.getComponentForElement(this.firstNode); + if (component && component.dueCardsList) { + return component.dueCardsList(); + } + return []; + }, + hasResults() { + const component = BlazeComponent.getComponentForElement(this.firstNode); + if (component && component.hasResults) { + return component.hasResults.get(); + } + return false; + }, + searching() { + const component = BlazeComponent.getComponentForElement(this.firstNode); + if (component && component.isLoading) { + return component.isLoading.get(); + } + return true; // Show loading by default + }, + hasQueryErrors() { + return false; // No longer using search, so always false + }, + errorMessages() { + return []; // No longer using search, so always empty + }, + cardsCount() { + const component = BlazeComponent.getComponentForElement(this.firstNode); + if (component && component.cardsCount) { + return component.cardsCount(); + } + return 0; + }, + resultsText() { + const component = BlazeComponent.getComponentForElement(this.firstNode); + if (component && component.resultsText) { + return component.resultsText(); + } + return ''; + }, }); -Template.dueCardsHeaderBar.events({ - 'click .js-due-cards-view-change': Popup.open('dueCardsViewChange'), -}); +BlazeComponent.extendComponent({ + events() { + return [ + { + 'click .js-due-cards-view-me'() { + if (Utils && Utils.setDueCardsView) { + Utils.setDueCardsView('me'); + } + Popup.back(); + }, -Template.dueCards.onCreated(function () { - this._cachedCards = null; - this._cachedTimestamp = null; - this.subscriptionHandle = null; - this.isLoading = new ReactiveVar(true); - this.hasResults = new ReactiveVar(false); - this.searching = new ReactiveVar(false); + 'click .js-due-cards-view-all'() { + if (Utils && Utils.setDueCardsView) { + Utils.setDueCardsView('all'); + } + Popup.back(); + }, + }, + ]; + }, +}).register('dueCardsViewChangePopup'); - const tpl = this; +class DueCardsComponent extends BlazeComponent { + onCreated() { + super.onCreated(); + + this._cachedCards = null; + this._cachedTimestamp = null; + this.subscriptionHandle = null; + this.isLoading = new ReactiveVar(true); + this.hasResults = new ReactiveVar(false); + this.searching = new ReactiveVar(false); + + // Subscribe to the optimized due cards publication + this.autorun(() => { + const allUsers = this.dueCardsView() === 'all'; + if (this.subscriptionHandle) { + this.subscriptionHandle.stop(); + } + this.subscriptionHandle = Meteor.subscribe('dueCards', allUsers); + + // Update loading state based on subscription + this.autorun(() => { + if (this.subscriptionHandle && this.subscriptionHandle.ready()) { + if (process.env.DEBUG === 'true') { + console.log('dueCards: subscription ready, loading data...'); + } + this.isLoading.set(false); + const cards = this.dueCardsList(); + this.hasResults.set(cards && cards.length > 0); + } else { + if (process.env.DEBUG === 'true') { + console.log('dueCards: subscription not ready, showing loading...'); + } + this.isLoading.set(true); + this.hasResults.set(false); + } + }); + }); + } - function dueCardsView() { + onDestroyed() { + super.onDestroyed(); + if (this.subscriptionHandle) { + this.subscriptionHandle.stop(); + } + } + + dueCardsView() { + // eslint-disable-next-line no-console + //console.log('sort:', Utils.dueCardsView()); return Utils && Utils.dueCardsView ? Utils.dueCardsView() : 'me'; } - function dueCardsList() { + sortByBoard() { + return this.dueCardsView() === 'board'; + } + + hasResults() { + return this.hasResults.get(); + } + + cardsCount() { + const cards = this.dueCardsList(); + return cards ? cards.length : 0; + } + + resultsText() { + const count = this.cardsCount(); + if (count === 1) { + return TAPi18n.__('one-card-found'); + } else { + // Get the translated text and manually replace %s with the count + const baseText = TAPi18n.__('n-cards-found'); + const result = baseText.replace('%s', count); + + if (process.env.DEBUG === 'true') { + console.log('dueCards: base text:', baseText, 'count:', count, 'result:', result); + } + return result; + } + } + + dueCardsList() { // Check if subscription is ready - if (!tpl.subscriptionHandle || !tpl.subscriptionHandle.ready()) { + if (!this.subscriptionHandle || !this.subscriptionHandle.ready()) { if (process.env.DEBUG === 'true') { console.log('dueCards client: subscription not ready'); } @@ -39,11 +180,11 @@ Template.dueCards.onCreated(function () { } // Use cached results if available to avoid expensive re-sorting - if (tpl._cachedCards && tpl._cachedTimestamp && (Date.now() - tpl._cachedTimestamp < 5000)) { + if (this._cachedCards && this._cachedTimestamp && (Date.now() - this._cachedTimestamp < 5000)) { if (process.env.DEBUG === 'true') { - console.log('dueCards client: using cached results,', tpl._cachedCards.length, 'cards'); + console.log('dueCards client: using cached results,', this._cachedCards.length, 'cards'); } - return tpl._cachedCards; + return this._cachedCards; } // Get cards directly from the subscription (already sorted by the publication) @@ -55,10 +196,10 @@ Template.dueCards.onCreated(function () { if (process.env.DEBUG === 'true') { console.log('dueCards client: found', cards.length, 'cards with due dates'); - console.log('dueCards client: cards details:', cards.map(c => ({ - id: c._id, - title: c.title, - dueAt: c.dueAt, + console.log('dueCards client: cards details:', cards.map(c => ({ + id: c._id, + title: c.title, + dueAt: c.dueAt, boardId: c.boardId, members: c.members, assignees: c.assignees, @@ -67,7 +208,7 @@ Template.dueCards.onCreated(function () { } // Filter cards based on user view preference - const allUsers = dueCardsView() === 'all'; + const allUsers = this.dueCardsView() === 'all'; const currentUser = ReactiveCache.getCurrentUser(); let filteredCards = cards; @@ -82,138 +223,29 @@ Template.dueCards.onCreated(function () { const isAssignee = card.assignees && card.assignees.includes(currentUser._id); const isAuthor = card.userId === currentUser._id; const matches = isMember || isAssignee || isAuthor; - + if (process.env.DEBUG === 'true' && matches) { console.log('dueCards client: card matches user:', card.title, { isMember, isAssignee, isAuthor }); } - + return matches; }); } - // Normalize dueAt to timestamps for stable client-side ordering - const future = new Date('2100-12-31').getTime(); - const toTime = v => { - if (v === null || v === undefined || v === '') return future; - if (v instanceof Date) return v.getTime(); - const t = new Date(v); - if (!isNaN(t.getTime())) return t.getTime(); - return future; - }; - - filteredCards.sort((a, b) => { - const x = toTime(a.dueAt); - const y = toTime(b.dueAt); - if (x > y) return 1; - if (x < y) return -1; - return 0; - }); - if (process.env.DEBUG === 'true') { console.log('dueCards client: filtered to', filteredCards.length, 'cards'); } // Cache the results for 5 seconds to avoid re-filtering on every render - tpl._cachedCards = filteredCards; - tpl._cachedTimestamp = Date.now(); + this._cachedCards = filteredCards; + this._cachedTimestamp = Date.now(); // Update reactive variables - tpl.hasResults.set(filteredCards && filteredCards.length > 0); - tpl.isLoading.set(false); + this.hasResults.set(filteredCards && filteredCards.length > 0); + this.isLoading.set(false); return filteredCards; } +} - // Store dueCardsList on the instance so helpers can call it - this.dueCardsList = dueCardsList; - this.dueCardsView = dueCardsView; - - // Subscribe to the optimized due cards publication - this.autorun(() => { - const allUsers = dueCardsView() === 'all'; - if (tpl.subscriptionHandle) { - tpl.subscriptionHandle.stop(); - } - tpl.subscriptionHandle = Meteor.subscribe('dueCards', allUsers); - - // Update loading state based on subscription - tpl.autorun(() => { - if (tpl.subscriptionHandle && tpl.subscriptionHandle.ready()) { - if (process.env.DEBUG === 'true') { - console.log('dueCards: subscription ready, loading data...'); - } - tpl.isLoading.set(false); - const cards = dueCardsList(); - tpl.hasResults.set(cards && cards.length > 0); - } else { - if (process.env.DEBUG === 'true') { - console.log('dueCards: subscription not ready, showing loading...'); - } - tpl.isLoading.set(true); - tpl.hasResults.set(false); - } - }); - }); -}); - -Template.dueCards.onDestroyed(function () { - if (this.subscriptionHandle) { - this.subscriptionHandle.stop(); - } -}); - -Template.dueCards.helpers({ - userId() { - return Meteor.userId(); - }, - // Return ReactiveVar so jade can use .get pattern - searching() { - return Template.instance().isLoading; - }, - hasResults() { - return Template.instance().hasResults; - }, - hasQueryErrors() { - return new ReactiveVar(false); - }, - errorMessages() { - return []; - }, - dueCardsList() { - const tpl = Template.instance(); - return tpl.dueCardsList ? tpl.dueCardsList() : []; - }, - resultsText() { - const tpl = Template.instance(); - const cards = tpl.dueCardsList ? tpl.dueCardsList() : []; - const count = cards ? cards.length : 0; - if (count === 1) { - return TAPi18n.__('one-card-found'); - } else { - // Get the translated text and manually replace %s with the count - const baseText = TAPi18n.__('n-cards-found'); - const result = baseText.replace('%s', count); - - if (process.env.DEBUG === 'true') { - console.log('dueCards: base text:', baseText, 'count:', count, 'result:', result); - } - return result; - } - }, -}); - -Template.dueCardsViewChangePopup.events({ - 'click .js-due-cards-view-me'() { - if (Utils && Utils.setDueCardsView) { - Utils.setDueCardsView('me'); - } - Popup.back(); - }, - - 'click .js-due-cards-view-all'() { - if (Utils && Utils.setDueCardsView) { - Utils.setDueCardsView('all'); - } - Popup.back(); - }, -}); +DueCardsComponent.register('dueCards'); diff --git a/client/components/main/editor.jade b/client/components/main/editor.jade index 4d7117ca3..802a8745d 100644 --- a/client/components/main/editor.jade +++ b/client/components/main/editor.jade @@ -1,6 +1,8 @@ template(name="editor") - a.fa.fa-brands.fa-markdown(title="{{_ 'convert-to-markdown'}}") - a.fa.fa-copy(title="{{_ 'copy-text-to-clipboard'}}") + a(title="{{_ 'convert-to-markdown'}}") + | 📝 + a(title="{{_ 'copy-text-to-clipboard'}}") + | 📋 span.copied-tooltip {{_ 'copied'}} textarea.editor( dir="auto" diff --git a/client/components/main/editor.js b/client/components/main/editor.js index 7fc1e78a0..9f15e5068 100644 --- a/client/components/main/editor.js +++ b/client/components/main/editor.js @@ -4,23 +4,13 @@ var converter = require('@wekanteam/html-to-markdown'); const specialHandles = [ {userId: 'board_members', username: 'board_members'}, - {userId: 'card_members', username: 'card_members'}, - {userId: 'board_assignees', username: 'board_assignees'}, - {userId: 'card_assignees', username: 'card_assignees'} -]; -const cardSpecialHandles = [ - {userId: 'card_members', username: 'card_members'}, - {userId: 'card_assignees', username: 'card_assignees'} -]; -const boardSpecialHandles = [ - {userId: 'board_members', username: 'board_members'}, - {userId: 'board_assignees', username: 'board_assignees'} + {userId: 'card_members', username: 'card_members'} ]; const specialHandleNames = specialHandles.map(m => m.username); -Template.editor.onRendered(function () { - const tpl = this; +BlazeComponent.extendComponent({ + onRendered() { // Start: Copy
     code https://github.com/wekan/wekan/issues/5149
         // TODO: Try to make copyPre visible at Card Details after editing or closing editor or Card Details.
         //       - Also this same TODO below at event, if someone gets it working.
    @@ -55,27 +45,23 @@ Template.editor.onRendered(function () {
             match: /\B@([\w.-]*)$/,
             search(term, callback) {
               const currentBoard = Utils.getCurrentBoard();
    -          const searchTerm = term.toLowerCase();
    -          const users = currentBoard
    -            .activeMembers()
    -            .map(member => {
    -              const user = ReactiveCache.getUser(member.userId);
    -              const username = user.username.toLowerCase();
    -              const fullName = user.profile && user.profile !== undefined && user.profile.fullname ? user.profile.fullname.toLowerCase() : "";
    -              return username.includes(searchTerm) || fullName.includes(searchTerm) ? user : null;
    -            })
    -            .filter(Boolean);
    -          // Order: 1. Users, 2. Card-specific options, 3. Board-wide options
    -          callback(_.union(users, cardSpecialHandles, boardSpecialHandles));
    +          callback(
    +            _.union(
    +            currentBoard
    +              .activeMembers()
    +              .map(member => {
    +                const user = ReactiveCache.getUser(member.userId);
    +                const username = user.username;
    +                const fullName = user.profile && user.profile !== undefined && user.profile.fullname ? user.profile.fullname : "";
    +                return username.includes(term) || fullName.includes(term) ? user : null;
    +              })
    +              .filter(Boolean), [...specialHandles])
    +          );
             },
             template(user) {
               if (user.profile && user.profile.fullname) {
                 return (user.profile.fullname + " (" + user.username + ")");
               }
    -          // Translate special group mentions
    -          if (specialHandleNames.includes(user.username)) {
    -            return TAPi18n.__(user.username);
    -          }
               return user.username;
             },
             replace(user) {
    @@ -89,7 +75,7 @@ Template.editor.onRendered(function () {
         ];
     
         const enableTextarea = function() {
    -      const $textarea = tpl.$(textareaSelector);
    +      const $textarea = this.$(textareaSelector);
           autosize($textarea);
           $textarea.escapeableTextComplete(mentions);
         };
    @@ -314,25 +300,29 @@ Template.editor.onRendered(function () {
           enableTextarea();
         }
         enableTextarea();
    -});
    +  },
    +  events() {
    +    return [
    +      {
    +        'click a.fa.fa-copy'(event) {
    +          const $editor = this.$('textarea.editor');
    +          const promise = Utils.copyTextToClipboard($editor[0].value);
     
    -Template.editor.events({
    -    'click a.fa.fa-copy'(event, tpl) {
    -      const $editor = tpl.$('textarea.editor');
    -      const promise = Utils.copyTextToClipboard($editor[0].value);
    -
    -      const $tooltip = tpl.$('.copied-tooltip');
    -      Utils.showCopied(promise, $tooltip);
    -    },
    -    'click a.fa.fa-brands.fa-markdown'(event, tpl) {
    -      const $editor = tpl.$('textarea.editor');
    -      $editor[0].value = converter.convert($editor[0].value);
    -    },
    -    // TODO: Try to make copyPre visible at Card Details after editing or closing editor or Card Details.
    -    //'click .js-close-inlined-form'(event) {
    -    //  Utils.copyPre();
    -    //},
    -});
    +          const $tooltip = this.$('.copied-tooltip');
    +          Utils.showCopied(promise, $tooltip);
    +        },
    +        'click a.fa.fa-brands.fa-markdown'(event) {
    +          const $editor = this.$('textarea.editor');
    +          $editor[0].value = converter.convert($editor[0].value);
    +        },
    +        // TODO: Try to make copyPre visible at Card Details after editing or closing editor or Card Details.
    +        //'click .js-close-inlined-form'(event) {
    +        //  Utils.copyPre();
    +        //},
    +      }
    +    ]
    +  }
    +}).register('editor');
     
     import DOMPurify from 'dompurify';
     import { sanitizeHTML } from '/imports/lib/secureDOMPurify';
    @@ -383,15 +373,13 @@ Blaze.Template.registerHelper(
         const currentBoard = Utils.getCurrentBoard();
         if (!currentBoard)
           return HTML.Raw(sanitizeHTML(content));
    -    const knowedUsers = _.union(currentBoard.members
    -      .filter(member => member.isActive)
    -      .map(member => {
    -        const u = ReactiveCache.getUser(member.userId);
    -        if (u) {
    -          member.username = u.username;
    -        }
    -        return member;
    -      }), [...specialHandles]);
    +    const knowedUsers = _.union(currentBoard.members.map(member => {
    +      const u = ReactiveCache.getUser(member.userId);
    +      if (u) {
    +        member.username = u.username;
    +      }
    +      return member;
    +    }), [...specialHandles]);
         const mentionRegex = /\B@([\w.-]*)/gi;
     
         let currentMention;
    @@ -408,14 +396,6 @@ Blaze.Template.registerHelper(
           if (knowedUser.userId === Meteor.userId()) {
             linkClass += ' me';
           }
    -
    -      // For special group mentions, display translated text
    -      let displayText = knowedUser.username;
    -      if (specialHandleNames.includes(knowedUser.username)) {
    -        displayText = TAPi18n.__(knowedUser.username);
    -        linkClass = 'atMention'; // Remove js-open-member for special handles
    -      }
    -
           // This @user mention link generation did open same Wekan
           // window in new tab, so now A is changed to U so it's
           // underlined and there is no link popup. This way also
    @@ -430,7 +410,7 @@ Blaze.Template.registerHelper(
               // using a data attribute.
               'data-userId': knowedUser.userId,
             },
    -        [' ', at, displayText],
    +        linkValue,
           );
     
           content = content.replace(fullMention, Blaze.toHTML(link));
    diff --git a/client/components/main/globalSearch.jade b/client/components/main/globalSearch.jade
    index df8a52a38..da00bbf03 100644
    --- a/client/components/main/globalSearch.jade
    +++ b/client/components/main/globalSearch.jade
    @@ -1,13 +1,13 @@
     template(name="globalSearchHeaderBar")
       if currentUser
         h1
    -      i.fa.fa-search
    +      | 🔍
           | {{_ 'globalSearch-title'}}
     
     template(name="globalSearchModalTitle")
       if currentUser
         h2
    -      i.fa.fa-keyboard-o
    +      | ⌨️
           | {{_ 'globalSearch-title'}}
     
     template(name="resultsPaged")
    diff --git a/client/components/main/globalSearch.js b/client/components/main/globalSearch.js
    index 200cb5f6e..3a8a84b52 100644
    --- a/client/components/main/globalSearch.js
    +++ b/client/components/main/globalSearch.js
    @@ -1,154 +1,114 @@
     import { TAPi18n } from '/imports/i18n';
    -import { CardSearchPaged } from '../../lib/cardSearch';
    +import { CardSearchPagedComponent } from '../../lib/cardSearch';
     import Boards from '../../../models/boards';
     import { Query, QueryErrors } from '../../../config/query-classes';
     
     // const subManager = new SubsManager();
     
    -Template.globalSearchHeaderBar.events({
    -  'click .js-due-cards-view-change': Popup.open('globalSearchViewChange'),
    -});
    -
    -Template.globalSearch.onCreated(function () {
    -  const search = new CardSearchPaged(this);
    -  this.search = search;
    -
    -  this.myLists = new ReactiveVar([]);
    -  this.myLabelNames = new ReactiveVar([]);
    -  this.myBoardNames = new ReactiveVar([]);
    -  this.parsingErrors = new QueryErrors();
    -  this.queryParams = null;
    -
    -  Meteor.call('myLists', (err, data) => {
    -    if (!err) {
    -      this.myLists.set(data);
    -    }
    -  });
    -
    -  Meteor.call('myLabelNames', (err, data) => {
    -    if (!err) {
    -      this.myLabelNames.set(data);
    -    }
    -  });
    -
    -  Meteor.call('myBoardNames', (err, data) => {
    -    if (!err) {
    -      this.myBoardNames.set(data);
    -    }
    -  });
    -});
    -
    -Template.globalSearch.onRendered(function () {
    -  Meteor.subscribe('setting');
    -
    -  // eslint-disable-next-line no-console
    -  //console.log('lang:', TAPi18n.getLanguage());
    -
    -  if (Session.get('globalQuery')) {
    -    searchAllBoards(this, Session.get('globalQuery'));
    -  }
    -});
    -
    -function searchAllBoards(tpl, queryText) {
    -  const search = tpl.search;
    -
    -  queryText = queryText.trim();
    -  // eslint-disable-next-line no-console
    -  //console.log('queryText:', queryText);
    -
    -  search.query.set(queryText);
    -
    -  search.resetSearch();
    -  tpl.parsingErrors = new QueryErrors();
    -
    -  if (!queryText) {
    -    return;
    -  }
    -
    -  search.searching.set(true);
    -
    -  const query = new Query();
    -  query.buildParams(queryText);
    -
    -  // eslint-disable-next-line no-console
    -  // console.log('params:', query.getParams());
    -
    -  tpl.queryParams = query.getQueryParams().getParams();
    -
    -  if (query.hasErrors()) {
    -    search.searching.set(false);
    -    search.queryErrors = query.errors();
    -    search.hasResults.set(true);
    -    search.hasQueryErrors.set(true);
    -    return;
    -  }
    -
    -  search.runGlobalSearch(query.getQueryParams());
    -}
    -
    -function errorMessages(tpl) {
    -  if (tpl.parsingErrors.hasErrors()) {
    -    return tpl.parsingErrors.errorMessages();
    -  }
    -  return tpl.search.queryErrorMessages();
    -}
    +BlazeComponent.extendComponent({
    +  events() {
    +    return [
    +      {
    +        'click .js-due-cards-view-change': Popup.open('globalSearchViewChange'),
    +      },
    +    ];
    +  },
    +}).register('globalSearchHeaderBar');
     
     Template.globalSearch.helpers({
       userId() {
         return Meteor.userId();
       },
    +});
     
    -  // Return ReactiveVars so jade can use .get pattern
    -  searching() {
    -    return Template.instance().search.searching;
    -  },
    -  hasResults() {
    -    return Template.instance().search.hasResults;
    -  },
    -  hasQueryErrors() {
    -    return Template.instance().search.hasQueryErrors;
    -  },
    -  serverError() {
    -    return Template.instance().search.serverError;
    -  },
    -  query() {
    -    return Template.instance().search.query;
    -  },
    -  debug() {
    -    return Template.instance().search.debug;
    -  },
    -  resultsHeading() {
    -    return Template.instance().search.resultsHeading;
    -  },
    -  results() {
    -    return Template.instance().search.results;
    -  },
    -  hasNextPage() {
    -    return Template.instance().search.hasNextPage;
    -  },
    -  hasPreviousPage() {
    -    return Template.instance().search.hasPreviousPage;
    -  },
    -  sessionData() {
    -    return Template.instance().search.sessionData;
    -  },
    -  getSearchHref() {
    -    return Template.instance().search.getSearchHref();
    -  },
    +class GlobalSearchComponent extends CardSearchPagedComponent {
    +  onCreated() {
    +    super.onCreated();
    +    this.myLists = new ReactiveVar([]);
    +    this.myLabelNames = new ReactiveVar([]);
    +    this.myBoardNames = new ReactiveVar([]);
    +    this.parsingErrors = new QueryErrors();
    +    this.queryParams = null;
     
    -  myLists() {
    -    return Template.instance().myLists;
    -  },
    -  myLabelNames() {
    -    return Template.instance().myLabelNames;
    -  },
    -  myBoardNames() {
    -    return Template.instance().myBoardNames;
    -  },
    +    Meteor.call('myLists', (err, data) => {
    +      if (!err) {
    +        this.myLists.set(data);
    +      }
    +    });
    +
    +    Meteor.call('myLabelNames', (err, data) => {
    +      if (!err) {
    +        this.myLabelNames.set(data);
    +      }
    +    });
    +
    +    Meteor.call('myBoardNames', (err, data) => {
    +      if (!err) {
    +        this.myBoardNames.set(data);
    +      }
    +    });
    +  }
    +
    +  onRendered() {
    +    Meteor.subscribe('setting');
    +
    +    // eslint-disable-next-line no-console
    +    //console.log('lang:', TAPi18n.getLanguage());
    +
    +    if (Session.get('globalQuery')) {
    +      this.searchAllBoards(Session.get('globalQuery'));
    +    }
    +  }
    +
    +  resetSearch() {
    +    super.resetSearch();
    +    this.parsingErrors = new QueryErrors();
    +  }
     
       errorMessages() {
    -    return errorMessages(Template.instance());
    -  },
    +    if (this.parsingErrors.hasErrors()) {
    +      return this.parsingErrors.errorMessages();
    +    }
    +    return this.queryErrorMessages();
    +  }
    +
    +  parsingErrorMessages() {
    +    this.parsingErrors.errorMessages();
    +  }
    +
    +  searchAllBoards(queryText) {
    +    queryText = queryText.trim();
    +    // eslint-disable-next-line no-console
    +    //console.log('queryText:', queryText);
    +
    +    this.query.set(queryText);
    +
    +    this.resetSearch();
    +
    +    if (!queryText) {
    +      return;
    +    }
    +
    +    this.searching.set(true);
    +
    +    const query = new Query();
    +    query.buildParams(queryText);
    +
    +    // eslint-disable-next-line no-console
    +    // console.log('params:', query.getParams());
    +
    +    this.queryParams = query.getQueryParams().getParams();
    +
    +    if (query.hasErrors()) {
    +      this.searching.set(false);
    +      this.queryErrors = query.errors();
    +      this.hasResults.set(true);
    +      this.hasQueryErrors.set(true);
    +      return;
    +    }
    +
    +    this.runGlobalSearch(query.getQueryParams());
    +  }
     
       searchInstructions() {
         const tags = {
    @@ -243,7 +203,7 @@ Template.globalSearch.helpers({
           .replace(/\>\*/, '\>\`')
         });
         return text;
    -  },
    +  }
     
       labelColors() {
         return Boards.simpleSchema()._schema['labels.$.color'].allowedValues.map(
    @@ -251,163 +211,89 @@ Template.globalSearch.helpers({
             return { color, name: TAPi18n.__(`color-${color}`) };
           },
         );
    -  },
    -});
    +  }
     
    -Template.globalSearch.events({
    -  'submit .js-search-query-form'(evt, tpl) {
    -    evt.preventDefault();
    -    searchAllBoards(tpl, evt.target.searchQuery.value);
    -  },
    -  'click .js-label-color'(evt, tpl) {
    -    evt.preventDefault();
    -    const input = document.getElementById('global-search-input');
    -    tpl.search.query.set(
    -      `${input.value} ${TAPi18n.__('operator-label')}:"${
    -        evt.currentTarget.textContent
    -      }"`,
    -    );
    -    document.getElementById('global-search-input').focus();
    -  },
    -  'click .js-copy-debug-selector'(evt) {
    -    /* Get the text field */
    -    const selector = document.getElementById("debug-selector");
    +  events() {
    +    return super.events().concat([
    +      {
    +        'submit .js-search-query-form'(evt) {
    +          evt.preventDefault();
    +          this.searchAllBoards(evt.target.searchQuery.value);
    +        },
    +        'click .js-label-color'(evt) {
    +          evt.preventDefault();
    +          const input = document.getElementById('global-search-input');
    +          this.query.set(
    +            `${input.value} ${TAPi18n.__('operator-label')}:"${
    +              evt.currentTarget.textContent
    +            }"`,
    +          );
    +          document.getElementById('global-search-input').focus();
    +        },
    +        'click .js-copy-debug-selector'(evt) {
    +          /* Get the text field */
    +          const selector = document.getElementById("debug-selector");
     
    -    try {
    -      navigator.clipboard.writeText(selector.textContent);
    -      alert("Selector copied to clipboard");
    -    } catch(err) {
    -      alert("Error copying text: " + err);
    -    }
    +          try {
    +            navigator.clipboard.writeText(selector.textContent);
    +            alert("Selector copied to clipboard");
    +          } catch(err) {
    +            alert("Error copying text: " + err);
    +          }
     
    -  },
    -  'click .js-copy-debug-projection'(evt) {
    -    /* Get the text field */
    -    const projection = document.getElementById("debug-projection");
    +        },
    +        'click .js-copy-debug-projection'(evt) {
    +          /* Get the text field */
    +          const projection = document.getElementById("debug-projection");
     
    -    try {
    -      navigator.clipboard.writeText(projection.textContent);
    -      alert("Projection copied to clipboard");
    -    } catch(err) {
    -      alert("Error copying text: " + err);
    -    }
    +          try {
    +            navigator.clipboard.writeText(projection.textContent);
    +            alert("Projection copied to clipboard");
    +          } catch(err) {
    +            alert("Error copying text: " + err);
    +          }
     
    -  },
    -  'click .js-board-title'(evt, tpl) {
    -    evt.preventDefault();
    -    const input = document.getElementById('global-search-input');
    -    tpl.search.query.set(
    -      `${input.value} ${TAPi18n.__('operator-board')}:"${
    -        evt.currentTarget.textContent
    -      }"`,
    -    );
    -    document.getElementById('global-search-input').focus();
    -  },
    -  'click .js-list-title'(evt, tpl) {
    -    evt.preventDefault();
    -    const input = document.getElementById('global-search-input');
    -    tpl.search.query.set(
    -      `${input.value} ${TAPi18n.__('operator-list')}:"${
    -        evt.currentTarget.textContent
    -      }"`,
    -    );
    -    document.getElementById('global-search-input').focus();
    -  },
    -  'click .js-label-name'(evt, tpl) {
    -    evt.preventDefault();
    -    const input = document.getElementById('global-search-input');
    -    tpl.search.query.set(
    -      `${input.value} ${TAPi18n.__('operator-label')}:"${
    -        evt.currentTarget.textContent
    -      }"`,
    -    );
    -    document.getElementById('global-search-input').focus();
    -  },
    -  'click .js-new-search'(evt, tpl) {
    -    evt.preventDefault();
    -    const input = document.getElementById('global-search-input');
    -    input.value = '';
    -    tpl.search.query.set('');
    -    tpl.search.hasResults.set(false);
    -  },
    -  'click .js-next-page'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.search.nextPage();
    -  },
    -  'click .js-previous-page'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.search.previousPage();
    -  },
    -});
    +        },
    +        'click .js-board-title'(evt) {
    +          evt.preventDefault();
    +          const input = document.getElementById('global-search-input');
    +          this.query.set(
    +            `${input.value} ${TAPi18n.__('operator-board')}:"${
    +              evt.currentTarget.textContent
    +            }"`,
    +          );
    +          document.getElementById('global-search-input').focus();
    +        },
    +        'click .js-list-title'(evt) {
    +          evt.preventDefault();
    +          const input = document.getElementById('global-search-input');
    +          this.query.set(
    +            `${input.value} ${TAPi18n.__('operator-list')}:"${
    +              evt.currentTarget.textContent
    +            }"`,
    +          );
    +          document.getElementById('global-search-input').focus();
    +        },
    +        'click .js-label-name'(evt) {
    +          evt.preventDefault();
    +          const input = document.getElementById('global-search-input');
    +          this.query.set(
    +            `${input.value} ${TAPi18n.__('operator-label')}:"${
    +              evt.currentTarget.textContent
    +            }"`,
    +          );
    +          document.getElementById('global-search-input').focus();
    +        },
    +        'click .js-new-search'(evt) {
    +          evt.preventDefault();
    +          const input = document.getElementById('global-search-input');
    +          input.value = '';
    +          this.query.set('');
    +          this.hasResults.set(false);
    +        },
    +      },
    +    ]);
    +  }
    +}
     
    -// resultsPaged template helpers - this template is used by globalSearch, brokenCards, and brokenCardsReport.
    -// It receives the parent's data context (which includes reactive vars) via +resultsPaged(this).
    -// The jade accesses resultsHeading.get, results.get, getSearchHref, hasPreviousPage.get, hasNextPage.get
    -// directly on the data context, so we don't need helpers for those when the parent passes
    -// the right data context. However, we need to ensure the helpers exist for the template.
    -Template.resultsPaged.helpers({
    -  resultsHeading() {
    -    const data = Template.currentData();
    -    if (data && data.resultsHeading) return data.resultsHeading;
    -    // fallback: check parent template
    -    const parentTpl = Template.instance().view.parentView.templateInstance && Template.instance().view.parentView.templateInstance();
    -    if (parentTpl && parentTpl.search) return parentTpl.search.resultsHeading;
    -    return new ReactiveVar('');
    -  },
    -  results() {
    -    const data = Template.currentData();
    -    if (data && data.results) return data.results;
    -    const parentTpl = Template.instance().view.parentView.templateInstance && Template.instance().view.parentView.templateInstance();
    -    if (parentTpl && parentTpl.search) return parentTpl.search.results;
    -    return new ReactiveVar([]);
    -  },
    -  getSearchHref() {
    -    const data = Template.currentData();
    -    if (data && data.getSearchHref) return data.getSearchHref();
    -    const parentTpl = Template.instance().view.parentView.templateInstance && Template.instance().view.parentView.templateInstance();
    -    if (parentTpl && parentTpl.search) return parentTpl.search.getSearchHref();
    -    return '';
    -  },
    -  hasPreviousPage() {
    -    const data = Template.currentData();
    -    if (data && data.hasPreviousPage) return data.hasPreviousPage;
    -    const parentTpl = Template.instance().view.parentView.templateInstance && Template.instance().view.parentView.templateInstance();
    -    if (parentTpl && parentTpl.search) return parentTpl.search.hasPreviousPage;
    -    return new ReactiveVar(false);
    -  },
    -  hasNextPage() {
    -    const data = Template.currentData();
    -    if (data && data.hasNextPage) return data.hasNextPage;
    -    const parentTpl = Template.instance().view.parentView.templateInstance && Template.instance().view.parentView.templateInstance();
    -    if (parentTpl && parentTpl.search) return parentTpl.search.hasNextPage;
    -    return new ReactiveVar(false);
    -  },
    -});
    -
    -Template.resultsPaged.events({
    -  'click .js-next-page'(evt) {
    -    evt.preventDefault();
    -    // Walk up to find the search instance
    -    let view = Template.instance().view;
    -    while (view) {
    -      const tplInst = view.templateInstance && view.templateInstance();
    -      if (tplInst && tplInst.search) {
    -        tplInst.search.nextPage();
    -        return;
    -      }
    -      view = view.parentView;
    -    }
    -  },
    -  'click .js-previous-page'(evt) {
    -    evt.preventDefault();
    -    let view = Template.instance().view;
    -    while (view) {
    -      const tplInst = view.templateInstance && view.templateInstance();
    -      if (tplInst && tplInst.search) {
    -        tplInst.search.previousPage();
    -        return;
    -      }
    -      view = view.parentView;
    -    }
    -  },
    -});
    +GlobalSearchComponent.register('globalSearch');
    diff --git a/client/components/main/header.css b/client/components/main/header.css
    index 450a72aeb..609941320 100644
    --- a/client/components/main/header.css
    +++ b/client/components/main/header.css
    @@ -78,26 +78,12 @@
     #header #header-main-bar .board-header-btn .board-header-btn-close i.fa {
       margin: 0 6px;
     }
    -#header #header-main-bar .board-header-btn .board-header-btn-icon {
    -  float: left;
    -  display: block;
    -  line-height: 28px;
    -  color: #27ae60;
    -  margin: 0 10px;
    -  cursor: pointer;
    -}
     #header #header-main-bar .board-header-btn.is-active,
     #header #header-main-bar h1.is-clickable.is-active,
     #header #header-main-bar .board-header-btn:hover:not(.is-disabled),
     #header #header-main-bar h1.is-clickable:hover:not(.is-disabled) {
       background: rgba(0,0,0,0.15);
     }
    -#header #header-main-bar .board-header-btn.js-multiselection-active {
    -  background: #1a5080;
    -}
    -#header #header-main-bar .board-header-btn.js-multiselection-active:hover {
    -  background: #0f3a5f;
    -}
     #header #header-main-bar .separator {
       margin: 2px 4px;
       border-left: 1px solid rgba(255,255,255,0.3);
    @@ -177,7 +163,8 @@
     }
     #header-quick-access ul.header-quick-access-list {
       transition: opacity 0.2s;
    -  overflow: hidden;
    +  overflow-x: auto;
    +  overflow-y: hidden;
       white-space: nowrap;
       padding: 10px;
       margin: -10px;
    @@ -185,16 +172,26 @@
       min-width: 0; /* Allow shrinking below content size */
       display: flex; /* Use flexbox for better control */
       align-items: center;
    +  scrollbar-width: thin; /* Firefox */
    +  scrollbar-color: rgba(255, 255, 255, 0.3) transparent; /* Firefox */
     }
     
    -/* Hide scrollbar completely */
    +/* Webkit scrollbar styling for better UX */
     #header-quick-access ul.header-quick-access-list::-webkit-scrollbar {
    -  display: none;
    +  height: 4px;
     }
     
    -#header-quick-access ul.header-quick-access-list {
    -  -ms-overflow-style: none; /* IE and Edge */
    -  scrollbar-width: none; /* Firefox */
    +#header-quick-access ul.header-quick-access-list::-webkit-scrollbar-track {
    +  background: transparent;
    +}
    +
    +#header-quick-access ul.header-quick-access-list::-webkit-scrollbar-thumb {
    +  background: rgba(255, 255, 255, 0.3);
    +  border-radius: 2px;
    +}
    +
    +#header-quick-access ul.header-quick-access-list::-webkit-scrollbar-thumb:hover {
    +  background: rgba(255, 255, 255, 0.5);
     }
     #header-quick-access ul.header-quick-access-list li {
       display: inline-block; /* Keep inline-block for proper spacing */
    @@ -222,13 +219,6 @@
     }
     #header-quick-access ul.header-quick-access-list li.current.empty {
       padding: 12px 10px 12px 10px;
    -  flex: 1;
    -  overflow: hidden;
    -  text-overflow: ellipsis;
    -  white-space: nowrap;
    -  cursor: default;
    -  opacity: 0.85;
    -  font-style: italic;
     }
     #header-quick-access ul.header-quick-access-list li:first-child .fa-home,
     #header-quick-access ul.header-quick-access-list li:nth-child(3) .fa-globe {
    @@ -349,20 +339,15 @@
           width: 100%;
           min-width: 3vw;
           font-size: clamp(12px, 2vw, 14px);
    -      box-sizing: border-box;
    -      -webkit-appearance: none;
    -      appearance: none;
    -      flex: 0 0 auto;
         }
     
         /* Make zoom input wider on all mobile screens */
         @media screen and (max-width: 800px),
            screen and (max-device-width: 932px) and (-webkit-min-device-pixel-ratio: 3) {
           #header-quick-access .zoom-controls .zoom-input {
    -        min-width: 80px !important; /* Wider on mobile to show 3 digits */
    -        width: 80px !important; /* Fixed width to show 100 fully */
    -        font-size: 16px !important; /* Slightly larger text */
    -        flex: 0 0 80px !important; /* Prevent shrinking in flex */
    +        min-width: 50px !important; /* Wider on mobile */
    +        width: 50px !important; /* Fixed width to show all numbers */
    +        font-size: 14px !important; /* Slightly larger text */
           }
         }
     
    @@ -865,9 +850,8 @@
         #header-quick-access .zoom-controls .zoom-input {
           font-size: 16px !important; /* Larger input text */
           padding: 0.5vh 0.8vw !important;
    -      min-width: 80px !important; /* Wider to fit 100 */
    -      width: 80px !important; /* Fixed width to show 100 fully */
    -      flex: 0 0 80px !important; /* Prevent shrinking in flex */
    +      min-width: 6vw !important; /* Much wider for mobile */
    +      width: 60px !important; /* Fixed width to show all numbers */
         }
     
         /* Make mobile mode toggle larger */
    diff --git a/client/components/main/header.jade b/client/components/main/header.jade
    index 3d3f5eb75..b7e870dc2 100644
    --- a/client/components/main/header.jade
    +++ b/client/components/main/header.jade
    @@ -9,7 +9,7 @@ template(name="header")
           // Home icon - always at left side of logo
           span.home-icon.allBoards
             a(href="{{pathFor 'home'}}")
    -          i.fa.fa-home
    +          | 🏠
               | {{_ 'all-boards'}}
     
           // Logo - visible; on mobile constrained by CSS
    @@ -30,14 +30,6 @@ template(name="header")
                 span.zoom-display {{zoomLevel}}%
                 input.zoom-input.js-zoom-input(type="number" value=zoomLevel min="50" max="300" step="10" style="display: none;")
     
    -      // Drag handles toggle - between zoom and mobile mode toggle
    -      a.board-header-btn.js-toggle-desktop-drag-handles(title="{{_ 'show-desktop-drag-handles'}}")
    -        i.fa.fa-arrows
    -        if isShowDesktopDragHandles
    -          i.fa.fa-check
    -        unless isShowDesktopDragHandles
    -          i.fa.fa-ban
    -
           if isMiniScreen
             ul.header-quick-access-list
               if currentList
    @@ -52,42 +44,48 @@ template(name="header")
                     a(href="{{pathFor 'board' id=_id slug=slug}}")
                       +viewer
                         = title
    +        //a.js-toggle-desktop-drag-handles(title="{{_ 'show-desktop-drag-handles'}}" alt="{{_ 'show-desktop-drag-handles'}}")
    +        //  i.fa.fa-arrows
    +        //    if isShowDesktopDragHandles
    +        //      i.fa.fa-check-square-o
    +        //    unless isShowDesktopDragHandles
    +        //      i.fa.fa-ban
             #header-new-board-icon
           else
             ul.header-quick-access-list
               //li
    -          //
    -            a(href="{{pathFor 'public'}}")
    -          //
    -              span.fa.fa-globe
    -          //
    -              | {{_ 'public'}}
    +          //  a(href="{{pathFor 'public'}}")
    +          //    span.fa.fa-globe
    +          //    | {{_ 'public'}}
               each currentUser.starredBoards
                 li(class="{{#if $.Session.equals 'currentBoard' _id}}current{{/if}}")
                   a(href="{{pathFor 'board' id=_id slug=slug}}")
                     +viewer
                       = title
               else
    -            li.current.empty(title="{{_ 'quick-access-description'}}")
    -            | {{_ 'quick-access-description'}}
    -        #header-new-board-icon
    +            li.current.empty {{_ 'quick-access-description'}}
    +        //a.js-toggle-desktop-drag-handles(title="{{_ 'show-desktop-drag-handles'}}" alt="{{_ 'show-desktop-drag-handles'}}")
    +        //  i.fa.fa-arrows
    +        //    if isShowDesktopDragHandles
    +        //      i.fa.fa-check-square-o
    +        //    unless isShowDesktopDragHandles
    +        //      i.fa.fa-ban
           // Next line is used only for spacing at header,
           // there is no visible clickable icon.
           #header-new-board-icon
    -      //
    -        Hide duplicate create board button,
    -      //
    -        because it did not show board templates correctly.
    +      //  Hide duplicate create board button,
    +      //  because it did not show board templates correctly.
           //a#header-new-board-icon.js-create-board
    -      //
    -        i.fa.fa-plus(title="Create a new board")
    +      //  i.fa.fa-plus(title="Create a new board")
     
           .mobile-mode-toggle
             a.board-header-btn.js-mobile-mode-toggle(title="{{_ 'mobile-desktop-toggle'}}" class="{{#if mobileMode}}mobile-active{{else}}desktop-active{{/if}}")
    -          i.mobile-icon(class="{{#if mobileMode}}active{{/if}}")
    -            i.fa.fa-mobile
    -          i.desktop-icon(class="{{#unless mobileMode}}active{{/unless}}")
    -            i.fa.fa-desktop
    +          i.mobile-icon(class="{{#if mobileMode}}active{{/if}}") 📱
    +          i.desktop-icon(class="{{#unless mobileMode}}active{{/unless}}") 🖥️
    +
    +      // Bookmarks button - desktop opens popup, mobile routes to page
    +      a.board-header-btn.js-open-bookmarks(title="{{_ 'bookmarks'}}")
    +        | 🔖
     
           // Notifications
           +notifications
    @@ -95,7 +93,7 @@ template(name="header")
           if currentSetting.customHelpLinkUrl
             #header-help
               a(href="{{currentSetting.customHelpLinkUrl}}", title="{{_ 'help'}}", target="_blank", rel="noopener noreferrer")
    -            i.fa.fa-question-circle
    +            | ❓
     
           +headerUserBar
     
    @@ -114,17 +112,15 @@ template(name="header")
         if hasAnnouncement
           .announcement
             p
    -          i.fa.fa-bullhorn
    +          | 📢
               +viewer
                 | #{announcement}
    -          a
    -            .js-close-announcement
    -              i.fa.fa-times-thin
    +          | ❌
     
     template(name="offlineWarning")
       .offline-warning
         p
    -      i.fa.fa-warning
    +      | ⚠️
           | {{_ 'app-is-offline'}}
     
           a.app-try-reconnect {{_ 'app-try-reconnect'}}
    diff --git a/client/components/main/header.js b/client/components/main/header.js
    index a0c451f4b..99c3aabc1 100644
    --- a/client/components/main/header.js
    +++ b/client/components/main/header.js
    @@ -1,11 +1,10 @@
     import { ReactiveCache } from '/imports/reactiveCache';
    -import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
     
     Meteor.subscribe('user-admin');
     Meteor.subscribe('boards');
     Meteor.subscribe('setting');
     Meteor.subscribe('announcements');
    -Template.header.onCreated(function () {
    +Template.header.onCreated(function(){
       const templateInstance = this;
       templateInstance.currentSetting = new ReactiveVar();
       templateInstance.isLoading = new ReactiveVar(false);
    @@ -14,21 +13,10 @@ Template.header.onCreated(function () {
         onReady() {
           templateInstance.currentSetting.set(ReactiveCache.getCurrentSetting());
           let currSetting = templateInstance.currentSetting.curValue;
    -      if (
    -        currSetting &&
    -        currSetting !== undefined &&
    -        currSetting.customLoginLogoImageUrl !== undefined &&
    -        document.getElementById('headerIsSettingDatabaseCallDone') != null
    -      )
    -        document.getElementById(
    -          'headerIsSettingDatabaseCallDone',
    -        ).style.display = 'none';
    -      else if (
    -        document.getElementById('headerIsSettingDatabaseCallDone') != null
    -      )
    -        document.getElementById(
    -          'headerIsSettingDatabaseCallDone',
    -        ).style.display = 'block';
    +      if(currSetting && currSetting !== undefined && currSetting.customLoginLogoImageUrl !== undefined && document.getElementById("headerIsSettingDatabaseCallDone") != null)
    +        document.getElementById("headerIsSettingDatabaseCallDone").style.display = 'none';
    +      else if(document.getElementById("headerIsSettingDatabaseCallDone") != null)
    +        document.getElementById("headerIsSettingDatabaseCallDone").style.display = 'block';
           return this.stop();
         },
       });
    @@ -86,15 +74,10 @@ Template.header.events({
       },
     
       'keypress .js-zoom-input'(evt) {
    -    if (evt.which === 13) {
    -      // Enter key
    +    if (evt.which === 13) { // Enter key
           const newZoomPercent = parseInt(evt.target.value);
     
    -      if (
    -        !isNaN(newZoomPercent) &&
    -        newZoomPercent >= 50 &&
    -        newZoomPercent <= 300
    -      ) {
    +      if (!isNaN(newZoomPercent) && newZoomPercent >= 50 && newZoomPercent <= 300) {
             const newZoom = newZoomPercent / 100;
             Utils.setZoomLevel(newZoom);
     
    @@ -132,10 +115,11 @@ Template.header.events({
         Session.set('currentCard', null);
       },
       'click .js-toggle-desktop-drag-handles'() {
    -    currentUser = Meteor.user();
    -    if (currentUser) {
    -      Meteor.call('toggleDesktopDragHandles');
    -    } else if (window.localStorage.getItem('showDesktopDragHandles')) {
    +    //currentUser = Meteor.user();
    +    //if (currentUser) {
    +    //  Meteor.call('toggleDesktopDragHandles');
    +    //} else if (window.localStorage.getItem('showDesktopDragHandles')) {
    +    if (window.localStorage.getItem('showDesktopDragHandles')) {
           window.localStorage.removeItem('showDesktopDragHandles');
           location.reload();
         } else {
    diff --git a/client/components/main/keyboardShortcuts.jade b/client/components/main/keyboardShortcuts.jade
    index 1d8ca981d..1eee1d220 100644
    --- a/client/components/main/keyboardShortcuts.jade
    +++ b/client/components/main/keyboardShortcuts.jade
    @@ -6,7 +6,7 @@ template(name="shortcutsHeaderBar")
     
     template(name="shortcutsModalTitle")
       h2
    -    i.fa.fa-keyboard-o
    +    | ⌨️
         | {{_ 'keyboard-shortcuts'}}
     
     template(name="keyboardShortcuts")
    diff --git a/client/components/main/layouts.css b/client/components/main/layouts.css
    index 5aed5a8c3..fb8f4bf5c 100644
    --- a/client/components/main/layouts.css
    +++ b/client/components/main/layouts.css
    @@ -81,27 +81,6 @@ body {
       display: flex;
       flex-direction: column;
       height: 100vh;
    -  /* iOS Safari fixes */
    -  -webkit-overflow-scrolling: touch;
    -}
    -
    -/* Mobile mode specific fixes for iOS Safari */
    -body.mobile-mode {
    -  overflow-x: hidden;
    -  position: fixed;
    -  width: 100%;
    -  height: 100vh;
    -  /* Prevent iOS Safari bounce scroll */
    -  overscroll-behavior: none;
    -  -webkit-overflow-scrolling: touch;
    -}
    -
    -/* Ensure content area is scrollable in mobile mode */
    -body.mobile-mode #content {
    -  overflow-y: auto;
    -  overflow-x: hidden;
    -  -webkit-overflow-scrolling: touch;
    -  height: calc(100vh - 48px);
     }
     #content {
       position: relative;
    @@ -477,39 +456,9 @@ a:not(.disabled).is-active i.fa {
     .viewer a:hover {
       color: #333;
     }
    -.basicTabs-container .tabs-list {
    -  text-align: left;
    -  width: 100%;
    -}
    -.basicTabs-container .tabs-list .tab-item {
    -  font-weight: 600;
    -  font-size: 13px;
    -  display: inline-block;
    -  padding: 13px 15px;
    -  margin: 0;
    -  list-style: none;
    -  cursor: pointer;
    -  user-select: none;
    -}
    -.basicTabs-container .tabs-list .tab-item.active {
    -  border-radius: 5px 5px 0 0;
    -  border: 1px solid #ddd;
    -  border-bottom: none;
    -  margin-bottom: -1px !important;
    -  padding: 12px 14px 14px 14px !important;
    -  background-color: #fff;
    -  color: #333;
    -}
     .basicTabs-container .tabs-content-container {
       padding: 0;
       padding-top: 15px;
    -  border-top: 1px solid #ddd;
    -}
    -.basicTabs-container .tabs-content {
    -  display: none;
    -}
    -.basicTabs-container .tabs-content.active {
    -  display: block;
     }
     .no-scrollbars {
       scrollbar-width: none;
    @@ -545,7 +494,7 @@ a:not(.disabled).is-active i.fa {
         max-width: 95vw;
         margin: 0 auto;
       }
    -
    +  
       /* Improve touch targets */
       button, .btn, .js-toggle, .js-color-choice, .js-reaction, .close {
         min-height: 44px;
    @@ -554,7 +503,7 @@ a:not(.disabled).is-active i.fa {
         font-size: 16px; /* Prevent zoom on iOS */
         touch-action: manipulation;
       }
    -
    +  
       /* Form elements */
       input, select, textarea {
         font-size: 16px; /* Prevent zoom on iOS */
    @@ -562,7 +511,7 @@ a:not(.disabled).is-active i.fa {
         min-height: 44px;
         touch-action: manipulation;
       }
    -
    +  
       /* Cards and lists */
       .minicard {
         min-height: 48px;
    @@ -570,19 +519,19 @@ a:not(.disabled).is-active i.fa {
         margin-bottom: 8px;
         touch-action: manipulation;
       }
    -
    +  
       .list {
         margin: 0 8px;
         min-width: 280px;
       }
    -
    +  
       /* Board canvas */
       .board-canvas {
    -    padding: 0 8px 8px 0;
    +    padding: 8px;
         overflow-x: auto;
         -webkit-overflow-scrolling: touch;
       }
    -
    +  
       /* Header mobile layout */
       #header {
         padding: 8px;
    @@ -591,7 +540,7 @@ a:not(.disabled).is-active i.fa {
         align-items: center;
         gap: 8px;
       }
    -
    +  
       #header-quick-access {
         /* Keep quick-access items in one row */
         display: flex;
    @@ -615,43 +564,43 @@ a:not(.disabled).is-active i.fa {
         overflow: hidden;
         white-space: nowrap;
       }
    -
    +  
       /* Hide text in home icon on mobile, show only icon */
       #header-quick-access .home-icon a span:not(.fa) {
         display: none !important;
       }
    -
    +  
       /* Ensure proper spacing for mobile header elements */
       #header-quick-access .zoom-controls {
         margin-left: auto;
         margin-right: 8px;
       }
    -
    +  
       .mobile-mode-toggle {
         margin-right: 8px;
       }
    -
    +  
       #header-user-bar {
         margin-left: auto;
       }
    -
    +  
       /* Ensure header elements don't wrap on very small screens */
       #header-quick-access {
         min-width: 0; /* Allow flexbox to shrink */
       }
    -
    +  
       /* Make sure logo doesn't take too much space on mobile */
       #header-quick-access img {
         max-height: 24px;
         max-width: 120px;
       }
    -
    +  
       /* Ensure zoom controls are compact on mobile */
       .zoom-controls .zoom-level {
         padding: 4px 8px;
         font-size: 12px;
       }
    -
    +  
       /* Modal mobile optimization */
       #modal .modal-content,
       #modal .modal-content-wide {
    @@ -662,7 +611,7 @@ a:not(.disabled).is-active i.fa {
         max-height: 90vh;
         overflow-y: auto;
       }
    -
    +  
       /* Table mobile optimization */
       table {
         font-size: 14px;
    @@ -672,19 +621,19 @@ a:not(.disabled).is-active i.fa {
         white-space: nowrap;
         -webkit-overflow-scrolling: touch;
       }
    -
    +  
       /* Admin panel mobile optimization */
       .setting-content .content-body {
         flex-direction: column;
         gap: 16px;
         padding: 8px;
       }
    -
    +  
       .setting-content .content-body .side-menu {
         width: 100%;
         order: 2;
       }
    -
    +  
       .setting-content .content-body .main-body {
         order: 1;
         min-height: 60vh;
    @@ -698,63 +647,58 @@ a:not(.disabled).is-active i.fa {
       #content > .wrapper {
         padding: 12px;
       }
    -
    +  
       .wrapper {
         padding: 12px;
       }
    -
    +  
       .panel-default {
         width: 90vw;
         max-width: 90vw;
       }
    -
    +  
       /* Touch-friendly but more compact */
       button, .btn, .js-toggle, .js-color-choice, .js-reaction, .close {
         min-height: 48px;
         min-width: 48px;
         padding: 10px 14px;
       }
    -
    +  
       .minicard {
         min-height: 40px;
         padding: 10px;
       }
    -
    +  
       .list {
         margin: 0 12px;
         min-width: 300px;
       }
    -
    +  
       .board-canvas {
    -    padding: 0 12px 12px 0;
    +    padding: 12px;
       }
    -
    +  
       #header {
         padding: 12px 16px;
       }
    -
    +  
       #modal .modal-content {
         width: 80vw;
         max-width: 600px;
       }
    -
    +  
       #modal .modal-content-wide {
         width: 90vw;
         max-width: 800px;
       }
    -
    +  
       .setting-content .content-body {
         gap: 20px;
       }
    -
    +  
       .setting-content .content-body .side-menu {
         width: 250px;
       }
    -
    -  /* Responsive handling for quick-access description on tablets */
    -  #header-quick-access ul.header-quick-access-list li.current.empty {
    -    max-width: 300px;
    -  }
     }
     
     /* Large displays and digital signage (1920px+) */
    @@ -762,49 +706,49 @@ a:not(.disabled).is-active i.fa {
       body {
         font-size: 18px;
       }
    -
    +  
       button, .btn, .js-toggle, .js-color-choice, .js-reaction, .close {
         min-height: 56px;
         min-width: 56px;
         padding: 16px 20px;
         font-size: 18px;
       }
    -
    +  
       .minicard {
         min-height: 56px;
         padding: 16px;
         font-size: 18px;
       }
    -
    +  
       .list {
         margin: 0 8px;
         min-width: 360px;
       }
    -
    +  
       .board-canvas {
         padding: 0;
       }
    -
    +  
       #header {
         padding: 0 8px;
       }
    -
    +  
       #content > .wrapper {
         padding: 0;
       }
    -
    +  
       #modal .modal-content {
         width: 600px;
       }
    -
    +  
       #modal .modal-content-wide {
         width: 1000px;
       }
    -
    +  
       .setting-content .content-body {
         gap: 32px;
       }
    -
    +  
       .setting-content .content-body .side-menu {
         width: 320px;
       }
    @@ -812,7 +756,7 @@ a:not(.disabled).is-active i.fa {
     .inline-input {
       height: 37px;
       margin: 8px 10px 0 0;
    -  width: 100px;
    +  width: 50px;
     }
     .select-authentication {
       width: 100%;
    @@ -955,40 +899,6 @@ a:not(.disabled).is-active i.fa {
         height: 100%;
       }
     }
    -
    -/* iOS Safari Mobile Mode Fixes */
    -@media screen and (max-width: 800px) {
    -  /* Prevent scrolling issues on iOS Safari when card popup is open */
    -  body.mobile-mode {
    -    overflow: hidden;
    -    position: fixed;
    -    width: 100%;
    -    height: 100vh;
    -  }
    -
    -  /* Fix z-index stacking for mobile Safari */
    -  body.mobile-mode .board-wrapper {
    -    z-index: 1;
    -  }
    -
    -  body.mobile-mode .board-wrapper .board-canvas .board-overlay {
    -    z-index: 17 !important;
    -  }
    -
    -  body.mobile-mode .card-details {
    -    z-index: 100 !important;
    -  }
    -
    -  body.mobile-mode .pop-over {
    -    z-index: 999;
    -  }
    -
    -  /* Ensure smooth scrolling on iOS */
    -  body.mobile-mode .card-details,
    -  body.mobile-mode .pop-over .content-wrapper {
    -    -webkit-overflow-scrolling: touch;
    -  }
    -}
     @-moz-keyframes lds-roller {
       0% {
         transform: rotate(0deg);
    diff --git a/client/components/main/layouts.jade b/client/components/main/layouts.jade
    index 7bd257fbd..469524e04 100644
    --- a/client/components/main/layouts.jade
    +++ b/client/components/main/layouts.jade
    @@ -2,10 +2,8 @@ template(name="main")
       html(lang="{{TAPi18n.getLanguage}}")
         head
           title
    -      meta(name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=yes, viewport-fit=cover")
    +      meta(name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=yes")
           meta(http-equiv="X-UA-Compatible" content="IE=edge")
    -      meta(name="apple-mobile-web-app-capable" content="yes")
    -      meta(name="apple-mobile-web-app-status-bar-style" content="black-translucent")
           //- XXX We should use pathFor in the following `href` to support the case
             where the application is deployed with a path prefix, but it seems to be
             difficult to do that cleanly with Blaze -- at least without adding extra
    @@ -69,15 +67,9 @@ template(name="userFormsLayout")
               select.select-lang.js-userform-set-language#userform-set-language-select(aria-label="{{_ 'changeLanguagePopup-title'}}")
                 each languages
                   if isCurrentLanguage
    -                if rtl
    -                  option(value="{{tag}}" selected="selected") {{name}} (RTL)
    -                else
    -                  option(value="{{tag}}" selected="selected") {{name}}
    +                option(value="{{tag}}" selected="selected") {{name}}
                   else
    -                if rtl
    -                  option(value="{{tag}}") {{name}} (RTL)
    -                else
    -                  option(value="{{tag}}") {{name}}
    +                option(value="{{tag}}") {{name}}
     
     template(name="defaultLayout")
       +header
    @@ -85,6 +77,7 @@ template(name="defaultLayout")
         | {{{afterBodyStart}}}
         +Template.dynamic(template=content)
         | {{{beforeBodyEnd}}}
    +  +migrationProgress
       +boardConversionProgress
       if (Modal.isOpen)
         #modal
    @@ -92,13 +85,13 @@ template(name="defaultLayout")
           if (Modal.isWide)
             .modal-content-wide.modal-container
               a.modal-close-btn.js-close-modal
    -            i.fa.fa-times-thin
    +            | ❌
               +Template.dynamic(template=Modal.getHeaderName)
               +Template.dynamic(template=Modal.getTemplateName)
           else
             .modal-content.modal-container
               a.modal-close-btn.js-close-modal
    -            i.fa.fa-times-thin
    +            | ❌
               +Template.dynamic(template=Modal.getHeaderName)
               +Template.dynamic(template=Modal.getTemplateName)
     
    @@ -109,7 +102,8 @@ template(name="message")
       .big-message.quiet(class=color)
         h1 {{_ label}}
         unless currentUser
    -      p {{{_ 'page-maybe-private' '/sign-in'}}}
    +      with(pathFor route='atSignIn')
    +        p {{{_ 'page-maybe-private' this}}}
     
     template(name="loader")
       h1.loadingText {{_ 'loading'}}
    diff --git a/client/components/main/layouts.js b/client/components/main/layouts.js
    index e2452849d..d2d535207 100644
    --- a/client/components/main/layouts.js
    +++ b/client/components/main/layouts.js
    @@ -1,6 +1,7 @@
     import { ReactiveCache } from '/imports/reactiveCache';
     import { TAPi18n } from '/imports/i18n';
    -import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
    +
    +BlazeLayout.setRoot('body');
     
     let alreadyCheck = 1;
     let isCheckDone = false;
    @@ -46,7 +47,7 @@ Template.userFormsLayout.onCreated(function () {
     
     Template.userFormsLayout.onRendered(() => {
       Meteor.call('getAuthenticationsEnabled', (_, result) => {
    -    let enabledAuthenticationMethods = ['password']; // we show/hide this based on isPasswordLoginEnabled
    +    let enabledAuthenticationMethods = [ 'password' ]; // we show/hide this based on isPasswordLoginEnabled
     
         if (result) {
           Object.keys(result).forEach((m) => {
    @@ -86,91 +87,17 @@ Template.userFormsLayout.onRendered(() => {
         );
         EscapeActions.executeAll();
     
    -    // Set up MutationObserver for OIDC button instead of deprecated DOMSubtreeModified
    -    const oidcButton = document.getElementById('at-oidc');
    -    if (oidcButton) {
    -      const observer = new MutationObserver((mutations) => {
    -        if (alreadyCheck <= 2) {
    -          let currSetting = ReactiveCache.getCurrentSetting();
    -          let oidcBtnElt = $('#at-oidc');
    -          if (
    -            currSetting &&
    -            currSetting !== undefined &&
    -            currSetting.oidcBtnText !== undefined &&
    -            oidcBtnElt != null &&
    -            oidcBtnElt != undefined
    -          ) {
    -            let htmlvalue = "" + currSetting.oidcBtnText;
    -            if (alreadyCheck == 1) {
    -              alreadyCheck++;
    -              oidcBtnElt.html('');
    -            } else {
    -              alreadyCheck++;
    -              oidcBtnElt.html(htmlvalue);
    -            }
    -          }
    -        } else {
    -          alreadyCheck = 1;
    -        }
    -      });
    -      observer.observe(oidcButton, { childList: true, subtree: true });
    -    }
    -
    -    // Set up MutationObserver for .at-form instead of deprecated DOMSubtreeModified
    -    const atForm = document.querySelector('.at-form');
    -    if (atForm) {
    -      const formObserver = new MutationObserver((mutations) => {
    -        if (alreadyCheck <= 2 && !isCheckDone) {
    -          if (document.getElementById('at-oidc') != null) {
    -            let currSetting = ReactiveCache.getCurrentSetting();
    -            let oidcBtnElt = $('#at-oidc');
    -            if (
    -              currSetting &&
    -              currSetting !== undefined &&
    -              currSetting.oidcBtnText !== undefined &&
    -              oidcBtnElt != null &&
    -              oidcBtnElt != undefined
    -            ) {
    -              let htmlvalue =
    -                "" + currSetting.oidcBtnText;
    -              if (alreadyCheck == 1) {
    -                alreadyCheck++;
    -                oidcBtnElt.html('');
    -              } else {
    -                alreadyCheck++;
    -                isCheckDone = true;
    -                oidcBtnElt.html(htmlvalue);
    -              }
    -            }
    -          }
    -        } else {
    -          alreadyCheck = 1;
    -        }
    -      });
    -      formObserver.observe(atForm, { childList: true, subtree: true });
    -    }
    -
         // Add autocomplete attribute to login input for WCAG compliance
    -    const loginInput = document.querySelector(
    -      'input[type="text"], input[type="email"]',
    -    );
    -    if (
    -      loginInput &&
    -      loginInput.name &&
    -      (loginInput.name.toLowerCase().includes('user') ||
    -        loginInput.name.toLowerCase().includes('email'))
    -    ) {
    +    const loginInput = document.querySelector('input[type="text"], input[type="email"]');
    +    if (loginInput && loginInput.name && (loginInput.name.toLowerCase().includes('user') || loginInput.name.toLowerCase().includes('email'))) {
           loginInput.setAttribute('autocomplete', 'username email');
         }
     
         // Add autocomplete attributes to password fields for WCAG compliance
         const passwordInputs = document.querySelectorAll('input[type="password"]');
    -    passwordInputs.forEach((input) => {
    +    passwordInputs.forEach(input => {
           if (input.name && input.name.includes('password')) {
    -        if (
    -          input.name.includes('password_again') ||
    -          input.name.includes('new_password')
    -        ) {
    +        if (input.name.includes('password_again') || input.name.includes('new_password')) {
               input.setAttribute('autocomplete', 'new-password');
             } else {
               input.setAttribute('autocomplete', 'current-password');
    @@ -184,18 +111,18 @@ Template.userFormsLayout.helpers({
       isLegalNoticeLinkExist() {
         const currSet = Template.instance().currentSetting.get();
         if (currSet && currSet !== undefined && currSet != null) {
    -      return (
    -        currSet.legalNotice !== undefined && currSet.legalNotice.trim() != ''
    -      );
    -    } else return false;
    +      return currSet.legalNotice !== undefined && currSet.legalNotice.trim() != "";
    +    }
    +    else
    +      return false;
       },
     
       getLegalNoticeWithWritTraduction() {
    -    let spanLegalNoticeElt = $('#legalNoticeSpan');
    +    let spanLegalNoticeElt = $("#legalNoticeSpan");
         if (spanLegalNoticeElt != null && spanLegalNoticeElt != undefined) {
           spanLegalNoticeElt.html(TAPi18n.__('acceptance_of_our_legalNotice', {}));
         }
    -    let atLinkLegalNoticeElt = $('#legalNoticeAtLink');
    +    let atLinkLegalNoticeElt = $("#legalNoticeAtLink");
         if (atLinkLegalNoticeElt != null && atLinkLegalNoticeElt != undefined) {
           atLinkLegalNoticeElt.html(TAPi18n.__('legalNotice', {}));
         }
    @@ -216,7 +143,7 @@ Template.userFormsLayout.helpers({
     
       languages() {
         return TAPi18n.getSupportedLanguages()
    -      .map(({ tag, name, rtl }) => ({ tag, name, rtl }))
    +      .map(({ tag, name }) => ({ tag: tag, name }))
           .sort((a, b) => {
             if (a.name === b.name) {
               return 0;
    @@ -247,6 +174,52 @@ Template.userFormsLayout.events({
         }
         isCheckDone = false;
       },
    +  'click #at-signUp'(event, templateInstance) {
    +    isCheckDone = false;
    +  },
    +  'DOMSubtreeModified #at-oidc'(event) {
    +    if (alreadyCheck <= 2) {
    +      let currSetting = ReactiveCache.getCurrentSetting();
    +      let oidcBtnElt = $("#at-oidc");
    +      if (currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined) {
    +        let htmlvalue = "" + currSetting.oidcBtnText;
    +        if (alreadyCheck == 1) {
    +          alreadyCheck++;
    +          oidcBtnElt.html("");
    +        }
    +        else {
    +          alreadyCheck++;
    +          oidcBtnElt.html(htmlvalue);
    +        }
    +      }
    +    }
    +    else {
    +      alreadyCheck = 1;
    +    }
    +  },
    +  'DOMSubtreeModified .at-form'(event) {
    +    if (alreadyCheck <= 2 && !isCheckDone) {
    +      if (document.getElementById("at-oidc") != null) {
    +        let currSetting = ReactiveCache.getCurrentSetting();
    +        let oidcBtnElt = $("#at-oidc");
    +        if (currSetting && currSetting !== undefined && currSetting.oidcBtnText !== undefined && oidcBtnElt != null && oidcBtnElt != undefined) {
    +          let htmlvalue = "" + currSetting.oidcBtnText;
    +          if (alreadyCheck == 1) {
    +            alreadyCheck++;
    +            oidcBtnElt.html("");
    +          }
    +          else {
    +            alreadyCheck++;
    +            isCheckDone = true;
    +            oidcBtnElt.html(htmlvalue);
    +          }
    +        }
    +      }
    +    }
    +    else {
    +      alreadyCheck = 1;
    +    }
    +  },
     });
     
     Template.defaultLayout.events({
    @@ -274,14 +247,14 @@ async function authentication(event, templateInstance) {
     
       switch (result) {
         case 'ldap':
    -      return new Promise((resolve) => {
    +      return new Promise(resolve => {
             Meteor.loginWithLDAP(match, password, function () {
               resolve(FlowRouter.go('/'));
             });
           });
     
         case 'saml':
    -      return new Promise((resolve) => {
    +      return new Promise(resolve => {
             const provider = Meteor.settings.public.SAML_PROVIDER;
             Meteor.loginWithSaml(
               {
    @@ -294,7 +267,7 @@ async function authentication(event, templateInstance) {
           });
     
         case 'cas':
    -      return new Promise((resolve) => {
    +      return new Promise(resolve => {
             Meteor.loginWithCas(match, password, function () {
               resolve(FlowRouter.go('/'));
             });
    @@ -306,15 +279,9 @@ async function authentication(event, templateInstance) {
     }
     
     function getAuthenticationMethod(
    -  settings,
    +  { displayAuthenticationMethod, defaultAuthenticationMethod },
       match,
     ) {
    -  if (!settings) {
    -    return getUserAuthenticationMethod(undefined, match);
    -  }
    -
    -  const { displayAuthenticationMethod, defaultAuthenticationMethod } = settings;
    -
       if (displayAuthenticationMethod) {
         return $('.select-authentication').val();
       }
    @@ -322,7 +289,7 @@ function getAuthenticationMethod(
     }
     
     function getUserAuthenticationMethod(defaultAuthenticationMethod, match) {
    -  return new Promise((resolve) => {
    +  return new Promise(resolve => {
         try {
           Meteor.subscribe('user-authenticationMethod', match, {
             onReady() {
    diff --git a/client/components/main/myCards.jade b/client/components/main/myCards.jade
    index 7d068c43f..86105ced4 100644
    --- a/client/components/main/myCards.jade
    +++ b/client/components/main/myCards.jade
    @@ -2,25 +2,24 @@ template(name="myCardsHeaderBar")
       if currentUser
         h1
           //a.back-btn(href="{{pathFor 'home'}}")
    -      //
    -        i.fa.fa-chevron-left
    -      i.fa.fa-list
    +      //  i.fa.fa-chevron-left
    +      | 📋
           | {{_ 'my-cards'}}
     
         .board-header-btns.left
           a.board-header-btn.js-my-cards-view-change(title="{{_ 'myCardsViewChange-title'}}")
    -        i.fa.fa-caret-down
    +        | ▼
             if $eq myCardsView 'boards'
    -          i.fa.fa-list
    +          | 📋
               | {{_ 'myCardsViewChange-choice-boards'}}
             if $eq myCardsView 'table'
    -          i.fa.fa-bar-chart
    +          | 📊
               | {{_ 'myCardsViewChange-choice-table'}}
     
     template(name="myCardsModalTitle")
       if currentUser
         h2
    -      i.fa.fa-keyboard-o
    +      | ⌨️
           | {{_ 'my-cards'}}
     
     template(name="myCards")
    @@ -73,8 +72,7 @@ template(name="myCards")
                                     .my-cards-card-title-table
                                       | {{card.title}}
                                       //a.minicard-wrapper(href=card.originRelativeUrl)
    -                                  //
    -                                    | {{card.title}}
    +                                  //  | {{card.title}}
                                   td
                                     | {{list.title}}
                                   td
    @@ -96,7 +94,7 @@ template(name="myCards")
                                             | {{labelName board label}}
                                     td
                                       if card.dueAt
    -                                    | {{ displayDate card.dueAt 'LLL' }}
    +                                    | {{ moment card.dueAt 'LLL' }}
     
     template(name="myCardsViewChangePopup")
       if currentUser
    @@ -104,15 +102,15 @@ template(name="myCardsViewChangePopup")
           li
             with "myCardsViewChange-choice-boards"
               a.js-my-cards-view-boards
    -            i.fa.fa-list
    +            | 📋
                 | {{_ 'myCardsViewChange-choice-boards'}}
                 if $eq Utils.myCardsView "boards"
    -              i.fa.fa-check
    +              | ✅
           hr
           li
             with "myCardsViewChange-choice-table"
               a.js-my-cards-view-table
    -            i.fa.fa-bar-chart
    +            | 📊
                 | {{_ 'myCardsViewChange-choice-table'}}
                 if $eq Utils.myCardsView "table"
    -              i.fa.fa-check
    +              | ✅
    diff --git a/client/components/main/myCards.js b/client/components/main/myCards.js
    index c765862ea..a6426b66a 100644
    --- a/client/components/main/myCards.js
    +++ b/client/components/main/myCards.js
    @@ -1,6 +1,6 @@
    -import { CardSearchPaged } from '../../lib/cardSearch';
    +import { CardSearchPagedComponent } from '../../lib/cardSearch';
     
    -Template.myCardsHeaderBar.helpers({
    +BlazeComponent.extendComponent({
       myCardsSort() {
         // eslint-disable-next-line no-console
         // console.log('sort:', Utils.myCardsSort());
    @@ -12,69 +12,86 @@ Template.myCardsHeaderBar.helpers({
         // console.log('sort:', Utils.myCardsView());
         return Utils.myCardsView();
       },
    -});
     
    -Template.myCardsHeaderBar.events({
    -  'click .js-toggle-my-cards-choose-sort': Popup.open(
    -    'myCardsSortChange',
    -  ),
    -  'click .js-my-cards-view-change': Popup.open(
    -    'myCardsViewChange'),
    -});
    -
    -Template.myCards.onCreated(function () {
    -  const search = new CardSearchPaged(this);
    -  this.search = search;
    -
    -  // Override getSubscription for myCards
    -  search.getSubscription = function (queryParams) {
    -    return Meteor.subscribe(
    -      'myCards',
    -      search.sessionId,
    -      search.subscriptionCallbacks,
    -    );
    -  };
    -
    -  search.runGlobalSearch(null);
    -  Meteor.subscribe('setting');
    -});
    +  events() {
    +    return [
    +      {
    +        'click .js-toggle-my-cards-choose-sort': Popup.open(
    +          'myCardsSortChange',
    +        ),
    +        'click .js-my-cards-view-change': Popup.open(
    +          'myCardsViewChange'),
    +      },
    +    ];
    +  },
    +}).register('myCardsHeaderBar');
     
     Template.myCards.helpers({
       userId() {
         return Meteor.userId();
       },
    +});
     
    -  // Return ReactiveVar so jade can use .get pattern
    -  searching() {
    -    return Template.instance().search.searching;
    +BlazeComponent.extendComponent({
    +  events() {
    +    return [
    +      {
    +        'click .js-my-cards-view-boards'() {
    +          Utils.setMyCardsView('boards');
    +          Popup.back();
    +        },
    +
    +        'click .js-my-cards-view-table'() {
    +          Utils.setMyCardsView('table');
    +          Popup.back();
    +        },
    +      },
    +    ];
       },
    +}).register('myCardsViewChangePopup');
    +
    +class MyCardsComponent extends CardSearchPagedComponent {
    +  onCreated() {
    +    super.onCreated();
    +
    +    this.runGlobalSearch(null);
    +    Meteor.subscribe('setting');
    +  }
    +
    +  // eslint-disable-next-line no-unused-vars
    +  getSubscription(queryParams) {
    +    return Meteor.subscribe(
    +      'myCards',
    +      this.sessionId,
    +      this.subscriptionCallbacks,
    +    );
    +  }
     
       myCardsView() {
         // eslint-disable-next-line no-console
         //console.log('sort:', Utils.myCardsView());
         return Utils.myCardsView();
    -  },
    +  }
     
       labelName(board, labelId) {
    -    const label = board.getLabelById(labelId);
    -    const name = label.name;
    -    return name;
    -  },
    +    const label = board.getLabelById(labelId)
    +    const name = label.name
    +    return name
    +  }
     
       labelColor(board, labelId) {
    -    const label = board.getLabelById(labelId);
    -    const color = label.color;
    -    return color;
    -  },
    +    const label = board.getLabelById(labelId)
    +    const color = label.color
    +    return color
    +  }
     
       myCardsList() {
    -    const search = Template.instance().search;
         const boards = [];
         let board = null;
         let swimlane = null;
         let list = null;
     
    -    const cursor = search.getResults();
    +    const cursor = this.getResults();
     
         if (cursor) {
           cursor.forEach(card => {
    @@ -160,28 +177,6 @@ Template.myCards.helpers({
         }
     
         return [];
    -  },
    -});
    -
    -Template.myCards.events({
    -  'click .js-next-page'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.search.nextPage();
    -  },
    -  'click .js-previous-page'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.search.previousPage();
    -  },
    -});
    -
    -Template.myCardsViewChangePopup.events({
    -  'click .js-my-cards-view-boards'() {
    -    Utils.setMyCardsView('boards');
    -    Popup.back();
    -  },
    -
    -  'click .js-my-cards-view-table'() {
    -    Utils.setMyCardsView('table');
    -    Popup.back();
    -  },
    -});
    +  }
    +}
    +MyCardsComponent.register('myCards');
    diff --git a/client/components/main/popup.css b/client/components/main/popup.css
    index 8c0a50a42..7ddba701c 100644
    --- a/client/components/main/popup.css
    +++ b/client/components/main/popup.css
    @@ -93,44 +93,25 @@
       max-height: inherit;
     }
     
    -/* Fix overflow in the Member Settings (member menu) popup:
    -   the popup itself gets a max-height inline style, but the header consumes space.
    -   Make the header overlay the scrollable area so the list can't spill out. */
    -.pop-over[data-popup="memberMenuPopup"] {
    -  overflow: hidden;
    -}
    -.pop-over[data-popup="memberMenuPopup"] > .header {
    -  position: absolute;
    -  top: 0;
    -  left: 0;
    -  right: 0;
    -  margin-bottom: 0;
    -  z-index: 1;
    -}
    -.pop-over[data-popup="memberMenuPopup"] > .content-wrapper {
    -  padding-top: calc(4.5vh + 1vh);
    -  box-sizing: border-box;
    -}
    -
     /* Admin edit popups: use full height */
    -.pop-over[data-popup="editUserPopup"],
    -.pop-over[data-popup="editOrgPopup"],
    -.pop-over[data-popup="editTeamPopup"] {
    +.pop-over[data-popup="editUser"],
    +.pop-over[data-popup="editOrg"],
    +.pop-over[data-popup="editTeam"] {
       height: calc(100vh - 20px) !important;
       max-height: calc(100vh - 20px) !important;
     }
     
    -.pop-over[data-popup="editUserPopup"] .content-wrapper,
    -.pop-over[data-popup="editOrgPopup"] .content-wrapper,
    -.pop-over[data-popup="editTeamPopup"] .content-wrapper {
    +.pop-over[data-popup="editUser"] .content-wrapper,
    +.pop-over[data-popup="editOrg"] .content-wrapper,
    +.pop-over[data-popup="editTeam"] .content-wrapper {
       max-height: calc(100vh - 80px) !important; /* Subtract header height */
       height: calc(100vh - 80px) !important;
       overflow-y: auto !important;
     }
     
    -.pop-over[data-popup="editUserPopup"] .content-container,
    -.pop-over[data-popup="editOrgPopup"] .content-container,
    -.pop-over[data-popup="editTeamPopup"] .content-container {
    +.pop-over[data-popup="editUser"] .content-container,
    +.pop-over[data-popup="editOrg"] .content-container,
    +.pop-over[data-popup="editTeam"] .content-container {
       max-height: calc(100vh - 80px) !important; /* Subtract header height */
       height: calc(100vh - 80px) !important;
     }
    @@ -142,7 +123,7 @@
     }
     
     /* Specific styling for language popup list */
    -.pop-over[data-popup="changeLanguagePopup"] .pop-over-list {
    +.pop-over[data-popup="changeLanguage"] .pop-over-list {
       max-height: none;
       overflow: visible;
       height: auto;
    @@ -150,69 +131,46 @@
     }
     
     /* Ensure content div in language popup contains all items */
    -.pop-over[data-popup="changeLanguagePopup"] .content {
    +.pop-over[data-popup="changeLanguage"] .content {
       height: auto;
    -  /* Remove forced min-height to avoid top gap */
    +  min-height: 100%;
       display: flex;
       flex-direction: column;
     }
     
    -/* Ensure hidden stack pages truly take no space */
    -.pop-over[data-popup="changeLanguagePopup"] .content.no-height {
    -  min-height: 0 !important;
    -  height: 0 !important;
    -  padding: 0 !important;
    -  margin: 0 !important;
    -  visibility: hidden !important;
    +/* Allow dynamic height for Change Language popup */
    +.pop-over[data-popup="changeLanguage"] .content-wrapper {
    +  max-height: inherit; /* Use dynamic height from JavaScript */
    +}
    +
    +.pop-over[data-popup="changeLanguage"] .content-container {
    +  max-height: inherit; /* Use dynamic height from JavaScript */
     }
     
     /* Make language popup extend to bottom of browser window */
    -.pop-over[data-popup="changeLanguagePopup"] {
    -  position: fixed !important;
    -  bottom: 0 !important;
    -  top: auto !important;
    -  left: auto !important;
    -  right: 20px !important;
    -  width: auto !important;
    -  max-width: 450px !important;
    -  height: 100vh !important;
    -  max-height: 100vh !important;
    -  min-height: 300px !important;
    -  display: flex !important;
    -  flex-direction: column !important;
    -  margin: 0 !important;
    +.pop-over[data-popup="changeLanguage"] {
    +  height: calc(100vh - 30px);
    +  min-height: 300px;
    +  /* Adjust positioning to move popup 30px higher */
    +  transform: translateY(-30px);
     }
     
    -/* Allow dynamic height for Change Language popup */
    -.pop-over[data-popup="changeLanguagePopup"] .header {
    -  flex-shrink: 0 !important;
    -  height: auto !important;
    +.pop-over[data-popup="changeLanguage"] .content-wrapper {
    +  height: calc(100% - 50px); /* Subtract header height more precisely */
    +  min-height: 250px;
    +  overflow-y: auto;
    +  max-height: none; /* Remove any max-height constraints */
    +  display: flex;
    +  flex-direction: column;
     }
     
    -.pop-over[data-popup="changeLanguagePopup"] .content-wrapper {
    -  flex: 1 !important;
    -  overflow-y: auto !important;
    -  overflow-x: hidden !important;
    -  min-height: 0 !important;
    -  max-height: none !important;
    -  height: auto !important;
    -  width: 100% !important;
    -}
    -
    -.pop-over[data-popup="changeLanguagePopup"] .content-container {
    -  height: auto !important;
    -  max-height: none !important;
    -  flex: 1 !important;
    -  display: flex !important;
    -  flex-direction: column !important;
    -  width: 100% !important;
    -}
    -
    -.pop-over[data-popup="changeLanguagePopup"] .content {
    -  height: auto !important;
    -  max-height: none !important;
    -  padding-bottom: 50px !important;
    -  width: 100% !important;
    +.pop-over[data-popup="changeLanguage"] .content-container {
    +  height: auto; /* Let content determine height */
    +  min-height: 250px;
    +  max-height: none; /* Remove any max-height constraints */
    +  flex: 1;
    +  display: flex;
    +  flex-direction: column;
     }
     
     /* Date popup sizing for native HTML inputs */
    @@ -335,8 +293,6 @@
       overflow-y: auto !important;
     }
     
    -
    -
     .pop-over[data-popup="editCardReceivedDatePopup"] .edit-date button,
     .pop-over[data-popup="editCardStartDatePopup"] .edit-date button,
     .pop-over[data-popup="editCardDueDatePopup"] .edit-date button,
    @@ -431,6 +387,9 @@
       margin: 0;
       visibility: hidden;
     }
    +.pop-over .quiet {
    +/*  padding: 6px 6px 4px;*/
    +}
     .pop-over.search-over {
       background: #f0f0f0;
       min-height: 14vh;
    @@ -497,7 +456,6 @@
     /*  flex-wrap:wrap;*/
       gap:5px;
       align-items: center;
    -  color: #000 !important;
     }
     .pop-over-list li > a > .member{
       align-self: flex-start;
    @@ -558,7 +516,6 @@
       position: absolute;
       top: 6px;
       right: 12px;
    -  color: #3cb500;
     }
     .pop-over-list .pop-over-list.checkable li.active a {
       padding-right: 28px;
    @@ -566,10 +523,6 @@
     .pop-over-list .pop-over-list.checkable li.active a .fa-check {
       display: block;
     }
    -/* Grey check icons when grey icons setting is enabled */
    -body.grey-icons-enabled .pop-over-list .pop-over-list.checkable .fa-check {
    -  color: #7a7a7a;
    -}
     .pop-over.miniprofile .header {
       border-bottom-color: transparent;
       height: 30px;
    @@ -615,10 +568,6 @@ body.grey-icons-enabled .pop-over-list .pop-over-list.checkable .fa-check {
         overflow: hidden;
         margin-top: 0px;
         border: 0px solid #dbdbdb;
    -    /* Ensure popups appear above card details on mobile */
    -    z-index: 999999 !important;
    -    /* iOS Safari scrolling fix */
    -    -webkit-overflow-scrolling: touch;
       }
       .pop-over .header {
         color: #fff;
    @@ -703,23 +652,3 @@ body.grey-icons-enabled .pop-over-list .pop-over-list.checkable .fa-check {
         transform: none !important;
       }
     }
    -
    -/* Force full-screen popups in mobile mode regardless of screen width */
    -body.mobile-mode .pop-over {
    -  position: fixed !important;
    -  top: 0 !important;
    -  left: 0 !important;
    -  right: 0 !important;
    -  bottom: 0 !important;
    -  width: 100vw !important;
    -  height: 100vh !important;
    -  max-width: 100vw !important;
    -  max-height: 100vh !important;
    -}
    -body.mobile-mode .pop-over .content-wrapper {
    -  width: 100% !important;
    -  height: calc(100vh - 48px) !important;
    -  max-height: calc(100vh - 48px) !important;
    -  overflow-y: auto !important;
    -  overflow-x: hidden !important;
    -}
    diff --git a/client/components/main/popup.tpl.jade b/client/components/main/popup.tpl.jade
    index 463b2a5d0..bcc5756e4 100644
    --- a/client/components/main/popup.tpl.jade
    +++ b/client/components/main/popup.tpl.jade
    @@ -2,14 +2,13 @@
       class="{{#unless title}}miniprofile{{/unless}}"
       class=currentBoard.colorClass
       class="{{#unless title}}no-title{{/unless}}"
    -  data-popup="{{popupName}}"
       style="left:{{offset.left}}px; top:{{offset.top}}px;{{#if offset.maxHeight}} max-height:{{offset.maxHeight}}px;{{/if}}")
       .header
         a.back-btn.js-back-view(class="{{#unless hasPopupParent}}is-hidden{{/unless}}")
    -      i.fa.fa-caret-left
    +      | ◀️
         span.header-title= title
         a.close-btn.js-close-pop-over
    -      i.fa.fa-times-thin
    +      | ❌
       .content-wrapper
         //-
           We display the all stack of popup content next to each other and move
    diff --git a/client/components/main/spinner.js b/client/components/main/spinner.js
    index 3fecc3302..2d7ab35f7 100644
    --- a/client/components/main/spinner.js
    +++ b/client/components/main/spinner.js
    @@ -1,13 +1,11 @@
    -import { getSpinnerTemplate } from '/client/lib/spinner';
    +import { Spinner } from '/client/lib/spinner';
     
    -Template.spinner.helpers({
    -  getSpinnerTemplate() {
    -    return getSpinnerTemplate();
    -  },
    -});
    +(class extends Spinner {
    +}.register('spinner'));
     
    -Template.spinnerRaw.helpers({
    +(class extends Spinner {
       getSpinnerTemplateRaw() {
    -    return getSpinnerTemplate() + 'Raw';
    -  },
    -});
    +    let ret = super.getSpinnerTemplate() + 'Raw';
    +    return ret;
    +  }
    +}.register('spinnerRaw'));
    diff --git a/client/components/main/support.jade b/client/components/main/support.jade
    deleted file mode 100644
    index 97529476b..000000000
    --- a/client/components/main/support.jade
    +++ /dev/null
    @@ -1,29 +0,0 @@
    -template(name="supportHeaderBar")
    -  h1
    -    if isSupportEnabled
    -      = supportTitle
    -    else
    -      | {{_ 'support'}}
    -
    -template(name="support")
    -  .support-page
    -    if isSupportPublic
    -      if isSupportEnabled
    -        .support-page-content
    -          +viewer
    -            | {{supportContent}}
    -      else
    -        .support-page-content
    -          | {{_ 'support-info-not-added-yet'}}
    -    else
    -      if currentUser
    -        if isSupportEnabled
    -          .support-page-content
    -            +viewer
    -              | {{supportContent}}
    -        else
    -          .support-page-content
    -            | {{_ 'support-info-not-added-yet'}}
    -      else
    -        .support-page-content
    -          | {{_ 'support-info-only-for-logged-in-users'}}
    diff --git a/client/components/main/support.js b/client/components/main/support.js
    deleted file mode 100644
    index e0cbf4829..000000000
    --- a/client/components/main/support.js
    +++ /dev/null
    @@ -1,39 +0,0 @@
    -import { ReactiveCache } from '/imports/reactiveCache';
    -import { TAPi18n } from '/imports/i18n';
    -
    -// Shared helpers for both support templates
    -const supportHelpers = {
    -  supportTitle() {
    -    const setting = ReactiveCache.getCurrentSetting();
    -    return setting && setting.supportTitle ? setting.supportTitle : TAPi18n.__('support');
    -  },
    -  supportContent() {
    -    const setting = ReactiveCache.getCurrentSetting();
    -    return setting && setting.supportPageText ? setting.supportPageText : TAPi18n.__('support-info-not-added-yet');
    -  },
    -  isSupportEnabled() {
    -    const setting = ReactiveCache.getCurrentSetting();
    -    return setting && setting.supportPageEnabled;
    -  },
    -  isSupportPublic() {
    -    const setting = ReactiveCache.getCurrentSetting();
    -    return setting && setting.supportPagePublic;
    -  }
    -};
    -
    -// Main support page component
    -Template.support.onCreated(function () {
    -  this.error = new ReactiveVar('');
    -  this.loading = new ReactiveVar(false);
    -
    -  Meteor.subscribe('setting');
    -});
    -
    -Template.support.helpers(supportHelpers);
    -
    -// Header bar component
    -Template.supportHeaderBar.onCreated(function () {
    -  Meteor.subscribe('setting');
    -});
    -
    -Template.supportHeaderBar.helpers(supportHelpers);
    diff --git a/client/components/settings/migrationProgress.css b/client/components/migrationProgress.css
    similarity index 89%
    rename from client/components/settings/migrationProgress.css
    rename to client/components/migrationProgress.css
    index 2c1c046ef..f3b9a45d4 100644
    --- a/client/components/settings/migrationProgress.css
    +++ b/client/components/migrationProgress.css
    @@ -209,15 +209,15 @@
         width: 95%;
         margin: 20px;
       }
    -
    +  
       .migration-progress-content {
         padding: 20px;
       }
    -
    +  
       .migration-progress-header {
         padding: 15px;
       }
    -
    +  
       .migration-progress-title {
         font-size: 16px;
       }
    @@ -229,73 +229,41 @@
         background: #2d3748;
         color: #e2e8f0;
       }
    -
    +  
       .migration-progress-overall-label,
       .migration-progress-step-label,
       .migration-progress-status-label {
         color: #e2e8f0;
       }
    -
    +  
       .migration-progress-status {
         background: #4a5568;
         border-left-color: #63b3ed;
       }
    -
    +  
       .migration-progress-status-text {
         color: #cbd5e0;
       }
    -
    +  
       .migration-progress-details {
         background: #2b6cb0;
         border-left-color: #4299e1;
       }
    -
    +  
       .migration-progress-details-label {
         color: #bee3f8;
       }
    -
    +  
       .migration-progress-details-text {
         color: #90cdf4;
       }
    -
    +  
       .migration-progress-footer {
         background: #4a5568;
         border-top-color: #718096;
       }
    -
    +  
       .migration-progress-note {
         color: #a0aec0;
       }
    -}
    -/* Attachment migration styles */
    -.attachment-item.migrating {
    -  position: relative;
    -  opacity: 0.7;
    -}
    -
    -.attachment-migration-overlay {
    -  position: absolute;
    -  top: 0;
    -  left: 0;
    -  right: 0;
    -  bottom: 0;
    -  background: rgba(255, 255, 255, 0.9);
    -  display: flex;
    -  flex-direction: column;
    -  align-items: center;
    -  justify-content: center;
    -  z-index: 10;
    -  border-radius: 4px;
    -}
    -
    -.migration-spinner {
    -  font-size: 24px;
    -  color: #007cba;
    -  margin-bottom: 8px;
    -}
    -
    -.migration-text {
    -  font-size: 12px;
    -  color: #666;
    -  text-align: center;
    -}
    +}
    \ No newline at end of file
    diff --git a/client/components/settings/migrationProgress.jade b/client/components/migrationProgress.jade
    similarity index 74%
    rename from client/components/settings/migrationProgress.jade
    rename to client/components/migrationProgress.jade
    index f142cb273..250e20920 100644
    --- a/client/components/settings/migrationProgress.jade
    +++ b/client/components/migrationProgress.jade
    @@ -4,41 +4,40 @@ template(name="migrationProgress")
           .migration-progress-modal
             .migration-progress-header
               h3.migration-progress-title
    -            i.fa.fa-recycle
    -            | {{_ 'migration-progress-title'}}
    +            | 🔄 Board Migration in Progress
               .migration-progress-close.js-close-migration-progress
    -            i.fa.fa-times-thin
    -
    +            | ❌
    +        
             .migration-progress-content
               .migration-progress-overall
                 .migration-progress-overall-label
    -              | {{_ 'migration-progress-overall'}}: {{currentStep}} {{_ 'of'}} {{totalSteps}} {{_ 'steps'}}
    +              | Overall Progress: {{currentStep}} of {{totalSteps}} steps
                 .migration-progress-overall-bar
                   .migration-progress-overall-fill(style="{{progressBarStyle}}")
                 .migration-progress-overall-percentage
                   | {{overallProgress}}%
    -
    +          
               .migration-progress-current-step
                 .migration-progress-step-label
    -              | {{_ 'migration-progress-current-step'}}: {{stepNameFormatted}}
    +              | Current Step: {{stepNameFormatted}}
                 .migration-progress-step-bar
                   .migration-progress-step-fill(style="{{stepProgressBarStyle}}")
                 .migration-progress-step-percentage
                   | {{stepProgress}}%
    -
    +          
               .migration-progress-status
                 .migration-progress-status-label
    -              | {{_ 'migration-progress-status'}}:
    +              | Status:
                 .migration-progress-status-text
                   | {{stepStatus}}
    -
    +          
               if stepDetailsFormatted
                 .migration-progress-details
                   .migration-progress-details-label
    -                | {{_ 'migration-progress-details'}}:
    +                | Details:
                   .migration-progress-details-text
                     | {{stepDetailsFormatted}}
    -
    +        
             .migration-progress-footer
               .migration-progress-note
    -            | {{_ 'migration-progress-note'}}
    \ No newline at end of file
    +            | Please wait while we migrate your board to the latest structure...
    \ No newline at end of file
    diff --git a/client/components/settings/migrationProgress.js b/client/components/migrationProgress.js
    similarity index 99%
    rename from client/components/settings/migrationProgress.js
    rename to client/components/migrationProgress.js
    index 683d1c9e7..7c4064d39 100644
    --- a/client/components/settings/migrationProgress.js
    +++ b/client/components/migrationProgress.js
    @@ -79,7 +79,7 @@ class MigrationProgressManager {
         isMigrating.set(false);
         migrationProgress.set(100);
         migrationStatus.set('Migration completed successfully!');
    -
    +    
         // Clear step details after a delay
         setTimeout(() => {
           migrationStepName.set('');
    @@ -178,7 +178,7 @@ Template.migrationProgress.helpers({
       stepNameFormatted() {
         const stepName = migrationStepName.get();
         if (!stepName) return '';
    -
    +    
         // Convert snake_case to Title Case
         return stepName
           .split('_')
    @@ -189,7 +189,7 @@ Template.migrationProgress.helpers({
       stepDetailsFormatted() {
         const details = migrationStepDetails.get();
         if (!details) return '';
    -
    +    
         const formatted = [];
         for (const [key, value] of Object.entries(details)) {
           const formattedKey = key
    @@ -199,7 +199,7 @@ Template.migrationProgress.helpers({
             .replace(/^\w/, c => c.toUpperCase());
           formatted.push(`${formattedKey}: ${value}`);
         }
    -
    +    
         return formatted.join(', ');
       }
     });
    diff --git a/client/components/mixins/infiniteScrolling.js b/client/components/mixins/infiniteScrolling.js
    new file mode 100644
    index 000000000..722774c48
    --- /dev/null
    +++ b/client/components/mixins/infiniteScrolling.js
    @@ -0,0 +1,34 @@
    +const peakAnticipation = 200;
    +
    +Mixins.InfiniteScrolling = BlazeComponent.extendComponent({
    +  onCreated() {
    +    this._nextPeak = Infinity;
    +  },
    +
    +  setNextPeak(v) {
    +    this._nextPeak = v;
    +  },
    +
    +  getNextPeak() {
    +    return this._nextPeak;
    +  },
    +
    +  resetNextPeak() {
    +    this._nextPeak = Infinity;
    +  },
    +
    +  events() {
    +    return [
    +      {
    +        scroll(evt) {
    +          const domElement = evt.currentTarget;
    +          let altitude = domElement.scrollTop + domElement.offsetHeight;
    +          altitude += peakAnticipation;
    +          if (altitude >= this.callFirstWith(null, 'getNextPeak')) {
    +            this.mixinParent().callFirstWith(null, 'reachNextPeak');
    +          }
    +        },
    +      },
    +    ];
    +  },
    +});
    diff --git a/client/components/notifications/notification.css b/client/components/notifications/notification.css
    index 4426a0f8d..0ced61d37 100644
    --- a/client/components/notifications/notification.css
    +++ b/client/components/notifications/notification.css
    @@ -12,23 +12,20 @@
       display: none;
     }
     #notifications-drawer .notification .read-status {
    -  width: 2vw;
    -  padding: 0 0.5vw 0 0;
    +  width: 4vw;
    +  padding: 0 1.3vw 0 0;
     }
     #notifications-drawer .notification .read-status input {
       width: 3vw;
       height: 3vw;
     }
     #notifications-drawer .notification .read-status .activity-type {
    -  margin: 8px 0 0;
    -  width: 1.2em;
    -  height: 1.2em;
    -  font-size: clamp(14px, 2vw, 17px);
    +  margin: 2vh 0 0;
    +  width: 2.2vw;
    +  height: 2.2vw;
    +  font-size: clamp(14px, 2.5vw, 17px);
       display: block;
    -  color: #000;
    -}
    -#notifications-drawer .notification .read-status .activity-type.hidden {
    -  display: none;
    +  color: #bbb;
     }
     #notifications-drawer .notification .details .activity a.member {
       margin: 0px 0px 0px 0px;
    @@ -56,13 +53,6 @@
       color: #999;
       font-style: italic;
     }
    -#notifications-drawer .notification .details .notification-date {
    -  margin-top: 4px;
    -}
    -#notifications-drawer .notification .details .notification-date small {
    -  font-size: 0.85em;
    -  color: #999;
    -}
     #notifications-drawer .notification .remove a:hover {
       color: #eb4646 !important;
     }
    diff --git a/client/components/notifications/notification.jade b/client/components/notifications/notification.jade
    index 8b9c854bb..c98bbdba5 100644
    --- a/client/components/notifications/notification.jade
    +++ b/client/components/notifications/notification.jade
    @@ -5,8 +5,6 @@ template(name='notification')
           +notificationIcon(activityData)
         .details
           +activity(activity=activityData mode='none')
    -      .notification-date
    -        small.quiet {{activityDate}}
         if read
           .remove
    -        a(title="{{_ 'delete'}}") 🗑️
    +        a.fa.fa-trash
    diff --git a/client/components/notifications/notification.js b/client/components/notifications/notification.js
    index 27a7ff793..220aa8c74 100644
    --- a/client/components/notifications/notification.js
    +++ b/client/components/notifications/notification.js
    @@ -1,17 +1,12 @@
     import { ReactiveCache } from '/imports/reactiveCache';
    -import { formatDateByUserPreference } from '/imports/lib/dateUtils';
     
     Template.notification.events({
       'click .read-status .materialCheckBox'() {
         const update = {};
    -    const newReadValue = this.read ? null : Date.now();
    -    update[`profile.notifications.${this.index}.read`] = newReadValue;
    -
    -    Users.update(Meteor.userId(), { $set: update }, (error, result) => {
    -      if (error) {
    -        console.error('Error updating notification:', error);
    -      }
    -    });
    +    update[`profile.notifications.${this.index}.read`] = this.read
    +      ? null
    +      : Date.now();
    +    Users.update(Meteor.userId(), { $set: update });
       },
       'click .remove a'() {
         ReactiveCache.getCurrentUser().removeNotification(this.activityData._id);
    @@ -32,22 +27,4 @@ Template.notification.helpers({
         const activity = ReactiveCache.getActivity(activityId);
         return activity && activity.userId;
       },
    -  activityDate() {
    -    const activity = this.activityData;
    -    if (!activity || !activity.createdAt) return '';
    -
    -    const user = ReactiveCache.getCurrentUser();
    -    if (!user) return '';
    -
    -    const dateObj = new Date(activity.createdAt);
    -    if (Number.isNaN(dateObj.getTime())) return '';
    -
    -    const dateFormat = user.getDateFormat ? user.getDateFormat() : 'YYYY-MM-DD';
    -    const datePart = formatDateByUserPreference(dateObj, dateFormat, false);
    -    const timePart = dateObj.toLocaleTimeString([], {
    -      hour: 'numeric',
    -      minute: '2-digit',
    -    });
    -    return `${datePart} ${timePart}`.trim();
    -  },
     });
    diff --git a/client/components/notifications/notificationIcon.jade b/client/components/notifications/notificationIcon.jade
    index 4df93a6cc..ff35739c4 100644
    --- a/client/components/notifications/notificationIcon.jade
    +++ b/client/components/notifications/notificationIcon.jade
    @@ -1,8 +1,8 @@
     template(name='notificationIcon')
       if($in activityType 'deleteAttachment' 'addAttachment')
    -    span.activity-type(title="attachment") 📎
    +    i.fa.fa-paperclip.activity-type(title="attachment")
       else if($in activityType 'createBoard' 'importBoard')
    -    span.activity-type(title="board") 🗂️
    +    i.fa.fa-chalkboard.activity-type(title="board")
     
       else if($in activityType 'createCard' 'importCard' 'moveCard')
         +cardNotificationIcon
    @@ -19,21 +19,21 @@ template(name='notificationIcon')
         //- DRY and consistant
     
       else if($in activityType 'checkedItem' 'uncheckedItem' 'addChecklistItem' 'removedChecklistItem')
    -    span.activity-type(title="checklist item") ☑️
    +    i.fa.fa-check-square.activity-type(title="checklist item")
       else if($in activityType 'addComment')
    -    span.activity-type.hidden(title="comment")
    +    i.fa.fa-comment-o.activity-type(title="comment")
       else if($in activityType 'createCustomField' 'setCustomField' 'unsetCustomField')
    -    span.activity-type(title="custom field") 🧩
    +    i.fa.fa-code.activity-type(title="custom field")
       else if($in activityType 'addedLabel' 'removedLabel')
    -    span.activity-type(title="label") 🏷️
    +    i.fa.fa-tag.activity-type(title="label")
       else if($in activityType 'a-startAt' 'a-receivedAt')
    -    span.activity-type(title="date") ⏰
    +    i.fa.fa-clock-o.activity-type(title="date")
       else if($in activityType 'a-dueAt' 'a-endAt')
    -    span.activity-type(title="date") ⏰
    +    i.fa.fa-clock-o.activity-type(title="date")
     
       else if($in activityType 'createList' 'removeList' 'archivedList')
         +listNotificationIcon
    -  else if($in activityType 'importList')
    +  else if($in activityType  'importList')
         +listNotificationIcon
         //- $in can only handle up to 3 cases so we have to break this case over 2 cases... use a simple template to keep it
         //- DRY and consistant
    @@ -41,17 +41,17 @@ template(name='notificationIcon')
         //- elswhere in the app we use fa-trello to indicate lists...
         //- i personally like fa-columns a bit better
       else if($in activityType 'unjoinMember' 'addBoardMember' 'joinMember' 'removeBoardMember')
    -    span.activity-type(title="member") 👤
    +    i.fa.fa-user.activity-type(title="member")
       else if($in activityType 'createSwimlane' 'archivedSwimlane')
    -    span.activity-type(title="swimlane") 🧭
    +    i.fa.fa-th-large.activity-type(title="swimlane")
       else
    -    span.activity-type(title="can't find icon for #{activityType}") 🐞
    +    i.fa.fa-bug.activity-type(title="can't find icon for #{activityType}")
     
     template(name='cardNotificationIcon')
    -  span.activity-type(title="card") 🗒️
    +  i.fa.fa-clone.activity-type(title="card")
     
     template(name='checklistNotificationIcon')
    -  span.activity-type(title="checklist") 📝
    +  i.fa.fa-list.activity-type(title="checklist")
     
     template(name='listNotificationIcon')
    -  span.activity-type(title="list") 📋
    +  i.fa.fa-columns.activity-type(title="list")
    diff --git a/client/components/notifications/notifications.jade b/client/components/notifications/notifications.jade
    index b2209a72c..75391f349 100644
    --- a/client/components/notifications/notifications.jade
    +++ b/client/components/notifications/notifications.jade
    @@ -1,6 +1,6 @@
     template(name='notifications')
       #notifications.board-header-btns.right
         a.notifications-drawer-toggle(class="{{#if $gt unreadNotifications 0}}alert{{/if}}" title="{{_ 'notifications'}}")
    -      i.fa.fa-bell
    +      | 🔔
         if $.Session.get 'showNotificationsDrawer'
           +notificationsDrawer(unreadNotifications=unreadNotifications)
    diff --git a/client/components/notifications/notificationsDrawer.css b/client/components/notifications/notificationsDrawer.css
    index fac7b9574..d8ffcb153 100644
    --- a/client/components/notifications/notificationsDrawer.css
    +++ b/client/components/notifications/notificationsDrawer.css
    @@ -23,66 +23,12 @@ section#notifications-drawer .header {
       border-bottom: 1px solid #dbdbdb;
       z-index: 2;
     }
    -section#notifications-drawer .header .notification-menu-toggle {
    +section#notifications-drawer .header .toggle-read {
       position: absolute;
       left: 16px;
    -  top: calc(50% - 12px);
    -  font-size: 20px;
    -  cursor: pointer;
    -  color: #333;
    -  line-height: 24px;
    -}
    -section#notifications-drawer .header .notification-menu-toggle:hover {
    +  top: calc(50% - 8px);
       color: #2980b9;
     }
    -section#notifications-drawer .header .notification-menu {
    -  position: absolute;
    -  left: 16px;
    -  top: 44px;
    -  background: white;
    -  border: 1px solid #dbdbdb;
    -  border-radius: 3px;
    -  box-shadow: 0 2px 8px rgba(0,0,0,0.15);
    -  min-width: 220px;
    -  z-index: 100;
    -  display: none;
    -}
    -section#notifications-drawer .header .notification-menu.is-open {
    -  display: block;
    -}
    -section#notifications-drawer .header .notification-menu .menu-section {
    -  padding: 4px 0;
    -}
    -section#notifications-drawer .header .notification-menu .menu-divider {
    -  border-top: 1px solid #dbdbdb;
    -  margin: 4px 0;
    -}
    -section#notifications-drawer .header .notification-menu .menu-item {
    -  display: flex;
    -  align-items: center;
    -  padding: 8px 12px;
    -  cursor: pointer;
    -  color: #333;
    -  white-space: nowrap;
    -}
    -section#notifications-drawer .header .notification-menu .menu-item:hover {
    -  background-color: #f5f5f5;
    -}
    -section#notifications-drawer .header .notification-menu .menu-item.selected {
    -  background-color: #e8f4f8;
    -}
    -section#notifications-drawer .header .notification-menu .menu-item .check-icon {
    -  width: 20px;
    -  min-width: 20px;
    -  margin-right: 8px;
    -  text-align: center;
    -  color: #2980b9;
    -  font-weight: bold;
    -}
    -section#notifications-drawer .header .notification-menu .menu-item .menu-icon {
    -  margin-right: 8px;
    -  font-size: 16px;
    -}
     section#notifications-drawer .header h5 {
       text-align: center;
       margin: 0;
    @@ -96,7 +42,22 @@ section#notifications-drawer .header .close {
       line-height: 24px;
       opacity: 1;
     }
    -
    +section#notifications-drawer .all-read,
    +section#notifications-drawer .remove-read {
    +  color: #2980b9;
    +  background-color: #fafafa;
    +  margin: 8px 16px 12px;
    +  display: inline-block;
    +}
    +section#notifications-drawer .remove-read {
    +  float: right;
    +}
    +section#notifications-drawer .remove-read:hover {
    +  color: #eb4646 !important;
    +}
    +section#notifications-drawer .remove-read:hover i.fa {
    +  color: inherit;
    +}
     section#notifications-drawer ul.notifications {
       display: block;
       padding: 0px 16px 0px 16px;
    diff --git a/client/components/notifications/notificationsDrawer.jade b/client/components/notifications/notificationsDrawer.jade
    index 0c6070459..2fd2bb229 100644
    --- a/client/components/notifications/notificationsDrawer.jade
    +++ b/client/components/notifications/notificationsDrawer.jade
    @@ -1,54 +1,20 @@
     template(name='notificationsDrawer')
       section#notifications-drawer(class="{{#if $.Session.get 'showReadNotifications'}}show-read{{/if}}")
         .header
    -      a.notification-menu-toggle
    -        i.fa.fa-bars
    -      .notification-menu(class="{{#if $.Session.get 'showNotificationMenu'}}is-open{{/if}}")
    -        .menu-section
    -          a.menu-item(class="{{#unless $.Session.get 'showReadNotifications'}}selected{{/unless}}")
    -            span.check-icon
    -              if $not $.Session.get 'showReadNotifications'
    -                i.fa.fa-check
    -            span.menu-icon
    -              i.fa.fa-envelope-open
    -            span {{_ 'filter-by-unread'}}
    -          a.menu-item(class="{{#if $.Session.get 'showReadNotifications'}}selected{{/if}}")
    -            span.check-icon
    -              if $.Session.get 'showReadNotifications'
    -                i.fa.fa-check
    -            span.menu-icon
    -              i.fa.fa-copy
    -            span {{_ 'view-all'}}
    -        .menu-divider
    -        .menu-section
    -          if($gt unreadNotifications 0)
    -            a.menu-item.mark-all-read
    -              span.check-icon
    -              span.menu-icon
    -                i.fa.fa-check
    -              span {{_ 'mark-all-as-read'}}
    -          if ($and ($.Session.get 'showReadNotifications') ($gt readNotifications 0))
    -            a.menu-item.mark-all-unread
    -              span.check-icon
    -              span.menu-icon
    -                i.fa.fa-envelope-open
    -              span {{_ 'mark-all-as-unread'}}
    -          if ($and ($.Session.get 'showReadNotifications') ($gt readNotifications 0))
    -            a.menu-item.delete-read
    -              span.check-icon
    -              span.menu-icon
    -                i.fa.fa-trash
    -              span {{_ 'remove-all-read'}}
    -          a.menu-item.delete-all
    -            span.check-icon
    -            span.menu-icon
    -              i.fa.fa-trash
    -            span {{_ 'delete-all-notifications'}}
    +      if $.Session.get 'showReadNotifications'
    +        a.toggle-read {{_ 'filter-by-unread'}}
    +      else
    +        a.toggle-read {{_ 'view-all'}}
           h5 {{_ 'notifications'}}
             if($gt unreadNotifications 0)
               |(#{unreadNotifications})
    -      a.close
    -        i.fa.fa-times-thin
    +      a.fa.fa-times-thin.close
         ul.notifications
    -      each notifications
    +      each transformedProfile.notifications
             +notification(activityData=activityObj index=dbIndex read=read)
    +    if($gt unreadNotifications 0)
    +      a.all-read {{_ 'mark-all-as-read'}}
    +    if ($and ($.Session.get 'showReadNotifications') ($gt readNotifications 0))
    +      a.remove-read
    +        i.fa.fa-trash
    +        | {{_ 'remove-all-read'}}
    diff --git a/client/components/notifications/notificationsDrawer.js b/client/components/notifications/notificationsDrawer.js
    index be94abea7..add14b129 100644
    --- a/client/components/notifications/notificationsDrawer.js
    +++ b/client/components/notifications/notificationsDrawer.js
    @@ -14,83 +14,41 @@ Template.notificationsDrawer.onCreated(function() {
     });
     
     Template.notificationsDrawer.helpers({
    -  notifications() {
    -    const user = ReactiveCache.getCurrentUser();
    -    return user ? user.notifications() : [];
    -  },
       transformedProfile() {
         return ReactiveCache.getCurrentUser();
       },
       readNotifications() {
    -    const user = ReactiveCache.getCurrentUser();
    -    const list = user ? user.notifications() : [];
    -    const readNotifications = _.filter(list, v => !!v.read);
    +    const readNotifications = _.filter(
    +      ReactiveCache.getCurrentUser().profile.notifications,
    +      v => !!v.read,
    +    );
         return readNotifications.length;
       },
     });
     
     Template.notificationsDrawer.events({
    -  'click .notification-menu-toggle'(event) {
    -    event.stopPropagation();
    -    Session.set('showNotificationMenu', !Session.get('showNotificationMenu'));
    -  },
    -  'click .notification-menu .menu-item'(event) {
    -    const target = event.currentTarget;
    -
    -    if (target.classList.contains('mark-all-read')) {
    -      const notifications = ReactiveCache.getCurrentUser().profile.notifications;
    -      for (const index in notifications) {
    -        if (notifications.hasOwnProperty(index) && !notifications[index].read) {
    -          const update = {};
    -          update[`profile.notifications.${index}.read`] = Date.now();
    -          Users.update(Meteor.userId(), { $set: update });
    -        }
    +  'click .all-read'() {
    +    const notifications = ReactiveCache.getCurrentUser().profile.notifications;
    +    for (const index in notifications) {
    +      if (notifications.hasOwnProperty(index) && !notifications[index].read) {
    +        const update = {};
    +        update[`profile.notifications.${index}.read`] = Date.now();
    +        Users.update(Meteor.userId(), { $set: update });
           }
    -      Session.set('showNotificationMenu', false);
    -    } else if (target.classList.contains('mark-all-unread')) {
    -      const notifications = ReactiveCache.getCurrentUser().profile.notifications;
    -      for (const index in notifications) {
    -        if (notifications.hasOwnProperty(index) && notifications[index].read) {
    -          const update = {};
    -          update[`profile.notifications.${index}.read`] = null;
    -          Users.update(Meteor.userId(), { $set: update });
    -        }
    -      }
    -      Session.set('showNotificationMenu', false);
    -    } else if (target.classList.contains('delete-read')) {
    -      const user = ReactiveCache.getCurrentUser();
    -      for (const notification of user.profile.notifications) {
    -        if (notification.read) {
    -          user.removeNotification(notification.activity);
    -        }
    -      }
    -      Session.set('showNotificationMenu', false);
    -    } else if (target.classList.contains('delete-all')) {
    -      if (confirm(TAPi18n.__('delete-all-notifications-confirm'))) {
    -        const user = ReactiveCache.getCurrentUser();
    -        const notificationsCopy = [...user.profile.notifications];
    -        for (const notification of notificationsCopy) {
    -          user.removeNotification(notification.activity);
    -        }
    -      }
    -      Session.set('showNotificationMenu', false);
    -    } else if (target.classList.contains('selected')) {
    -      // Already selected, do nothing
    -      Session.set('showNotificationMenu', false);
    -    } else {
    -      // Toggle view
    -      Session.set('showReadNotifications', !Session.get('showReadNotifications'));
    -      Session.set('showNotificationMenu', false);
         }
       },
       'click .close'() {
    -    Session.set('showNotificationMenu', false);
         toggleNotificationsDrawer();
       },
    -  'click'(event) {
    -    // Close menu when clicking outside
    -    if (!event.target.closest('.notification-menu') && !event.target.closest('.notification-menu-toggle')) {
    -      Session.set('showNotificationMenu', false);
    +  'click .toggle-read'() {
    +    Session.set('showReadNotifications', !Session.get('showReadNotifications'));
    +  },
    +  'click .remove-read'() {
    +    const user = ReactiveCache.getCurrentUser();
    +    for (const notification of user.profile.notifications) {
    +      if (notification.read) {
    +        user.removeNotification(notification.activity);
    +      }
         }
       },
     });
    diff --git a/client/components/rules/actions/boardActions.jade b/client/components/rules/actions/boardActions.jade
    index 1a49d5eca..6f63635fa 100644
    --- a/client/components/rules/actions/boardActions.jade
    +++ b/client/components/rules/actions/boardActions.jade
    @@ -10,7 +10,7 @@ template(name="boardActions")
           div.trigger-text
             | {{_'r-its-list'}}
         div.trigger-button.js-add-gen-move-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    @@ -24,7 +24,6 @@ template(name="boardActions")
             | {{_'r-the-board'}}
           div.trigger-dropdown
             select(id="board-id")
    -          option(value="" disabled selected if=not boards.length) {{loadingBoardsLabel}}
               each boards
                 if $eq _id currentBoard._id
                   option(value="{{_id}}" selected) {{_ 'current'}}
    @@ -39,7 +38,7 @@ template(name="boardActions")
           div.trigger-dropdown
             input(id="swimlaneName",type=text,placeholder="{{_'r-name'}}")
         div.trigger-button.js-add-spec-move-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    @@ -50,7 +49,7 @@ template(name="boardActions")
           div.trigger-text
             | {{_'r-card'}}
         div.trigger-button.js-add-arch-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    @@ -59,7 +58,7 @@ template(name="boardActions")
           div.trigger-dropdown
             input(id="swimlane-name",type=text,placeholder="{{_'r-name'}}")
         div.trigger-button.js-add-swimlane-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    @@ -76,7 +75,7 @@ template(name="boardActions")
           div.trigger-dropdown
             input(id="swimlane-name2",type=text,placeholder="{{_'r-name'}}")
         div.trigger-button.js-create-card-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    @@ -86,7 +85,6 @@ template(name="boardActions")
             | {{_'r-the-board'}}
           div.trigger-dropdown
             select(id="board-id-link")
    -          option(value="" disabled selected if=not boards.length) {{loadingBoardsLabel}}
               each boards
                 if $eq _id currentBoard._id
                   option(value="{{_id}}" selected) {{_ 'current'}}
    @@ -101,7 +99,7 @@ template(name="boardActions")
           div.trigger-dropdown
             input(id="swimlaneName-link",type=text,placeholder="{{_'r-name'}}")
         div.trigger-button.js-link-card-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕
     
     
     
    diff --git a/client/components/rules/actions/boardActions.js b/client/components/rules/actions/boardActions.js
    index 2eae0c8a6..b69e9f476 100644
    --- a/client/components/rules/actions/boardActions.js
    +++ b/client/components/rules/actions/boardActions.js
    @@ -1,11 +1,8 @@
     import { ReactiveCache } from '/imports/reactiveCache';
    -import { TAPi18n } from '/imports/i18n';
     
    -Template.boardActions.onCreated(function () {
    -  this.subscribe('boards');
    -});
    +BlazeComponent.extendComponent({
    +  onCreated() {},
     
    -Template.boardActions.helpers({
       boards() {
         const ret = ReactiveCache.getBoards(
           {
    @@ -22,204 +19,192 @@ Template.boardActions.helpers({
         return ret;
       },
     
    -  loadingBoardsLabel() {
    -    try {
    -      const txt = TAPi18n.__('loading-boards');
    -      if (txt && !txt.startsWith("key '")) return txt;
    -    } catch (e) {
    -      // ignore translation lookup errors
    -    }
    -    return 'Loading boards...';
    +  events() {
    +    return [
    +      {
    +        'click .js-create-card-action'(event) {
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const cardName = this.find('#card-name').value;
    +          const listName = this.find('#list-name').value;
    +          const swimlaneName = this.find('#swimlane-name2').value;
    +          const boardId = Session.get('currentBoard');
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const triggerId = Triggers.insert(trigger);
    +          const actionId = Actions.insert({
    +            actionType: 'createCard',
    +            swimlaneName,
    +            cardName,
    +            listName,
    +            boardId,
    +            desc,
    +          });
    +          Rules.insert({
    +            title: ruleName,
    +            triggerId,
    +            actionId,
    +            boardId,
    +          });
    +        },
    +        'click .js-add-swimlane-action'(event) {
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const swimlaneName = this.find('#swimlane-name').value;
    +          const boardId = Session.get('currentBoard');
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const triggerId = Triggers.insert(trigger);
    +          const actionId = Actions.insert({
    +            actionType: 'addSwimlane',
    +            swimlaneName,
    +            boardId,
    +            desc,
    +          });
    +          Rules.insert({
    +            title: ruleName,
    +            triggerId,
    +            actionId,
    +            boardId,
    +          });
    +        },
    +        'click .js-add-spec-move-action'(event) {
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const actionSelected = this.find('#move-spec-action').value;
    +          const swimlaneName = this.find('#swimlaneName').value || '*';
    +          const listName = this.find('#listName').value || '*';
    +          const boardId = Session.get('currentBoard');
    +          const destBoardId = this.find('#board-id').value;
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          if (actionSelected === 'top') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'moveCardToTop',
    +              listName,
    +              swimlaneName,
    +              boardId: destBoardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +            });
    +          }
    +          if (actionSelected === 'bottom') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'moveCardToBottom',
    +              listName,
    +              swimlaneName,
    +              boardId: destBoardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +            });
    +          }
    +        },
    +        'click .js-add-gen-move-action'(event) {
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const boardId = Session.get('currentBoard');
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const actionSelected = this.find('#move-gen-action').value;
    +          if (actionSelected === 'top') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'moveCardToTop',
    +              listTitle: '*',
    +              boardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +            });
    +          }
    +          if (actionSelected === 'bottom') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'moveCardToBottom',
    +              listTitle: '*',
    +              boardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +            });
    +          }
    +        },
    +        'click .js-add-arch-action'(event) {
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const boardId = Session.get('currentBoard');
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const actionSelected = this.find('#arch-action').value;
    +          if (actionSelected === 'archive') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'archive',
    +              boardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +            });
    +          }
    +          if (actionSelected === 'unarchive') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'unarchive',
    +              boardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +            });
    +          }
    +        },
    +        'click .js-link-card-action'(event) {
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const swimlaneName = this.find('#swimlaneName-link').value || '*';
    +          const listName = this.find('#listName-link').value || '*';
    +          const boardId = Session.get('currentBoard');
    +          const destBoardId = this.find('#board-id-link').value;
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const triggerId = Triggers.insert(trigger);
    +          const actionId = Actions.insert({
    +            actionType: 'linkCard',
    +            listName,
    +            swimlaneName,
    +            boardId: destBoardId,
    +            desc,
    +          });
    +          Rules.insert({
    +            title: ruleName,
    +            triggerId,
    +            actionId,
    +            boardId,
    +          });
    +        },
    +      },
    +    ];
       },
    -});
    -
    -Template.boardActions.events({
    -  'click .js-create-card-action'(event, tpl) {
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const cardName = tpl.find('#card-name').value;
    -    const listName = tpl.find('#list-name').value;
    -    const swimlaneName = tpl.find('#swimlane-name2').value;
    -    const boardId = Session.get('currentBoard');
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const triggerId = Triggers.insert(trigger);
    -    const actionId = Actions.insert({
    -      actionType: 'createCard',
    -      swimlaneName,
    -      cardName,
    -      listName,
    -      boardId,
    -      desc,
    -    });
    -    Rules.insert({
    -      title: ruleName,
    -      triggerId,
    -      actionId,
    -      boardId,
    -    });
    -  },
    -  'click .js-add-swimlane-action'(event, tpl) {
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const swimlaneName = tpl.find('#swimlane-name').value;
    -    const boardId = Session.get('currentBoard');
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const triggerId = Triggers.insert(trigger);
    -    const actionId = Actions.insert({
    -      actionType: 'addSwimlane',
    -      swimlaneName,
    -      boardId,
    -      desc,
    -    });
    -    Rules.insert({
    -      title: ruleName,
    -      triggerId,
    -      actionId,
    -      boardId,
    -    });
    -  },
    -  'click .js-add-spec-move-action'(event, tpl) {
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const actionSelected = tpl.find('#move-spec-action').value;
    -    const swimlaneName = tpl.find('#swimlaneName').value || '*';
    -    const listName = tpl.find('#listName').value || '*';
    -    const boardId = Session.get('currentBoard');
    -    const destBoardId = tpl.find('#board-id').value;
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    if (actionSelected === 'top') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'moveCardToTop',
    -        listName,
    -        swimlaneName,
    -        boardId: destBoardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -      });
    -    }
    -    if (actionSelected === 'bottom') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'moveCardToBottom',
    -        listName,
    -        swimlaneName,
    -        boardId: destBoardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -      });
    -    }
    -  },
    -  'click .js-add-gen-move-action'(event, tpl) {
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const boardId = Session.get('currentBoard');
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const actionSelected = tpl.find('#move-gen-action').value;
    -    if (actionSelected === 'top') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'moveCardToTop',
    -        listTitle: '*',
    -        boardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -      });
    -    }
    -    if (actionSelected === 'bottom') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'moveCardToBottom',
    -        listTitle: '*',
    -        boardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -      });
    -    }
    -  },
    -  'click .js-add-arch-action'(event, tpl) {
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const boardId = Session.get('currentBoard');
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const actionSelected = tpl.find('#arch-action').value;
    -    if (actionSelected === 'archive') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'archive',
    -        boardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -      });
    -    }
    -    if (actionSelected === 'unarchive') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'unarchive',
    -        boardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -      });
    -    }
    -  },
    -  'click .js-link-card-action'(event, tpl) {
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const swimlaneName = tpl.find('#swimlaneName-link').value || '*';
    -    const listName = tpl.find('#listName-link').value || '*';
    -    const boardId = Session.get('currentBoard');
    -    const destBoardId = tpl.find('#board-id-link').value;
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const triggerId = Triggers.insert(trigger);
    -    const actionId = Actions.insert({
    -      actionType: 'linkCard',
    -      listName,
    -      swimlaneName,
    -      boardId: destBoardId,
    -      desc,
    -    });
    -    Rules.insert({
    -      title: ruleName,
    -      triggerId,
    -      actionId,
    -      boardId,
    -    });
    -  },
    -});
    +}).register('boardActions');
     /* eslint-no-undef */
    diff --git a/client/components/rules/actions/cardActions.jade b/client/components/rules/actions/cardActions.jade
    index aa31ca6da..baaf883af 100644
    --- a/client/components/rules/actions/cardActions.jade
    +++ b/client/components/rules/actions/cardActions.jade
    @@ -16,7 +16,7 @@ template(name="cardActions")
           div.trigger-text
             | {{_'r-to-current-datetime'}}
         div.trigger-button.js-set-date-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    @@ -30,7 +30,7 @@ template(name="cardActions")
               option(value="endAt") {{_'r-df-end-at'}}
               option(value="receivedAt") {{_'r-df-received-at'}}
         div.trigger-button.js-remove-datevalue-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    @@ -46,7 +46,7 @@ template(name="cardActions")
                 option(value="#{_id}")
                   = name
         div.trigger-button.js-add-label-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    @@ -59,14 +59,14 @@ template(name="cardActions")
           div.trigger-dropdown
             input(id="member-name",type=text,placeholder="{{_'r-name'}}")
         div.trigger-button.js-add-member-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
           div.trigger-text
             | {{_'r-remove-all'}}
         div.trigger-button.js-add-removeall-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    @@ -77,12 +77,12 @@ template(name="cardActions")
               class="card-details-{{cardColorButton}}")
               | {{_ cardColorButtonText }}
         div.trigger-button.js-set-color-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕
     
     template(name="setCardActionsColorPopup")
       form.edit-label
         .palette-colors: each colors
           span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
             if(isSelected color)
    -          i.fa.fa-check
    +          | ✅
         button.primary.confirm.js-submit {{_ 'save'}}
    diff --git a/client/components/rules/actions/cardActions.js b/client/components/rules/actions/cardActions.js
    index e6bfdade4..cb1b76734 100644
    --- a/client/components/rules/actions/cardActions.js
    +++ b/client/components/rules/actions/cardActions.js
    @@ -3,23 +3,18 @@ Meteor.startup(() => {
       cardColors = Cards.simpleSchema()._schema.color.allowedValues;
     });
     
    -// Module-level shared state so the color popup can read/write the
    -// cardColorButtonValue without relying on BlazeComponent.getOpenerComponent().
    -let sharedCardColorButtonValue;
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.subscribe('allRules');
    +    this.cardColorButtonValue = new ReactiveVar('green');
    +  },
     
    -Template.cardActions.onCreated(function () {
    -  this.subscribe('allRules');
    -  this.cardColorButtonValue = new ReactiveVar('green');
    -  sharedCardColorButtonValue = this.cardColorButtonValue;
    -});
    -
    -Template.cardActions.helpers({
       cardColorButton() {
    -    return Template.instance().cardColorButtonValue.get();
    +    return this.cardColorButtonValue.get();
       },
     
       cardColorButtonText() {
    -    return `color-${Template.instance().cardColorButtonValue.get()}`;
    +    return `color-${this.cardColorButtonValue.get()}`;
       },
     
       labels() {
    @@ -31,214 +26,215 @@ Template.cardActions.helpers({
         }
         return labels;
       },
    -});
     
    -Template.cardActions.events({
    -  'click .js-set-date-action'(event, tpl) {
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const triggerId = Triggers.insert(trigger);
    -    const actionSelected = tpl.find('#setdate-action').value;
    -    const dateFieldSelected = tpl.find('#setdate-datefield').value;
    -    const boardId = Session.get('currentBoard');
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    +  events() {
    +    return [
    +      {
    +        'click .js-set-date-action'(event) {
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const triggerId = Triggers.insert(trigger);
    +          const actionSelected = this.find('#setdate-action').value;
    +          const dateFieldSelected = this.find('#setdate-datefield').value;
    +          const boardId = Session.get('currentBoard');
    +          const desc = Utils.getTriggerActionDesc(event, this);
     
    -    const actionId = Actions.insert({
    -      actionType: actionSelected,
    -      dateField: dateFieldSelected,
    -      boardId,
    -      desc,
    -    });
    +          const actionId = Actions.insert({
    +            actionType: actionSelected,
    +            dateField: dateFieldSelected,
    +            boardId,
    +            desc,
    +          });
     
    -    Rules.insert({
    -      title: ruleName,
    -      triggerId,
    -      actionId,
    -      boardId,
    -      desc,
    -    });
    +          Rules.insert({
    +            title: ruleName,
    +            triggerId,
    +            actionId,
    +            boardId,
    +            desc,
    +          });
    +        },
    +
    +        'click .js-remove-datevalue-action'(event) {
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const triggerId = Triggers.insert(trigger);
    +          const dateFieldSelected = this.find('#setdate-removedatefieldvalue')
    +            .value;
    +          const boardId = Session.get('currentBoard');
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +
    +          const actionId = Actions.insert({
    +            actionType: 'removeDate',
    +            dateField: dateFieldSelected,
    +            boardId,
    +            desc,
    +          });
    +
    +          Rules.insert({
    +            title: ruleName,
    +            triggerId,
    +            actionId,
    +            boardId,
    +            desc,
    +          });
    +        },
    +        'click .js-add-label-action'(event) {
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const actionSelected = this.find('#label-action').value;
    +          const labelId = this.find('#label-id').value;
    +          const boardId = Session.get('currentBoard');
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          if (actionSelected === 'add') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'addLabel',
    +              labelId,
    +              boardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +            });
    +          }
    +          if (actionSelected === 'remove') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'removeLabel',
    +              labelId,
    +              boardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +            });
    +          }
    +        },
    +        'click .js-add-member-action'(event) {
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const actionSelected = this.find('#member-action').value;
    +          const username = this.find('#member-name').value;
    +          const boardId = Session.get('currentBoard');
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          if (actionSelected === 'add') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'addMember',
    +              username,
    +              boardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +              desc,
    +            });
    +          }
    +          if (actionSelected === 'remove') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'removeMember',
    +              username,
    +              boardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +            });
    +          }
    +        },
    +        'click .js-add-removeall-action'(event) {
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const triggerId = Triggers.insert(trigger);
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const boardId = Session.get('currentBoard');
    +          const actionId = Actions.insert({
    +            actionType: 'removeMember',
    +            //  deepcode ignore NoHardcodedCredentials: it's no credential
    +            username: '*',
    +            boardId,
    +            desc,
    +          });
    +          Rules.insert({
    +            title: ruleName,
    +            triggerId,
    +            actionId,
    +            boardId,
    +          });
    +        },
    +        'click .js-show-color-palette'(event) {
    +          const funct = Popup.open('setCardActionsColor');
    +          const colorButton = this.find('#color-action');
    +          if (colorButton.value === '') {
    +            colorButton.value = 'green';
    +          }
    +          funct.call(this, event);
    +        },
    +        'click .js-set-color-action'(event) {
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const selectedColor = this.cardColorButtonValue.get();
    +          const boardId = Session.get('currentBoard');
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const triggerId = Triggers.insert(trigger);
    +          const actionId = Actions.insert({
    +            actionType: 'setColor',
    +            selectedColor,
    +            boardId,
    +            desc,
    +          });
    +          Rules.insert({
    +            title: ruleName,
    +            triggerId,
    +            actionId,
    +            boardId,
    +          });
    +        },
    +      },
    +    ];
    +  },
    +}).register('cardActions');
    +
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.currentAction = this.currentData();
    +    this.colorButtonValue = Popup.getOpenerComponent().cardColorButtonValue;
    +    this.currentColor = new ReactiveVar(this.colorButtonValue.get());
       },
     
    -  'click .js-remove-datevalue-action'(event, tpl) {
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const triggerId = Triggers.insert(trigger);
    -    const dateFieldSelected = tpl.find('#setdate-removedatefieldvalue')
    -      .value;
    -    const boardId = Session.get('currentBoard');
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -
    -    const actionId = Actions.insert({
    -      actionType: 'removeDate',
    -      dateField: dateFieldSelected,
    -      boardId,
    -      desc,
    -    });
    -
    -    Rules.insert({
    -      title: ruleName,
    -      triggerId,
    -      actionId,
    -      boardId,
    -      desc,
    -    });
    -  },
    -  'click .js-add-label-action'(event, tpl) {
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const actionSelected = tpl.find('#label-action').value;
    -    const labelId = tpl.find('#label-id').value;
    -    const boardId = Session.get('currentBoard');
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    if (actionSelected === 'add') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'addLabel',
    -        labelId,
    -        boardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -      });
    -    }
    -    if (actionSelected === 'remove') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'removeLabel',
    -        labelId,
    -        boardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -      });
    -    }
    -  },
    -  'click .js-add-member-action'(event, tpl) {
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const actionSelected = tpl.find('#member-action').value;
    -    const username = tpl.find('#member-name').value;
    -    const boardId = Session.get('currentBoard');
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    if (actionSelected === 'add') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'addMember',
    -        username,
    -        boardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -        desc,
    -      });
    -    }
    -    if (actionSelected === 'remove') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'removeMember',
    -        username,
    -        boardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -      });
    -    }
    -  },
    -  'click .js-add-removeall-action'(event, tpl) {
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const triggerId = Triggers.insert(trigger);
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const boardId = Session.get('currentBoard');
    -    const actionId = Actions.insert({
    -      actionType: 'removeMember',
    -      //  deepcode ignore NoHardcodedCredentials: it's no credential
    -      username: '*',
    -      boardId,
    -      desc,
    -    });
    -    Rules.insert({
    -      title: ruleName,
    -      triggerId,
    -      actionId,
    -      boardId,
    -    });
    -  },
    -  'click .js-show-color-palette'(event, tpl) {
    -    const funct = Popup.open('setCardActionsColor');
    -    const colorButton = tpl.find('#color-action');
    -    if (colorButton.value === '') {
    -      colorButton.value = 'green';
    -    }
    -    funct.call(this, event);
    -  },
    -  'click .js-set-color-action'(event, tpl) {
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const selectedColor = tpl.cardColorButtonValue.get();
    -    const boardId = Session.get('currentBoard');
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const triggerId = Triggers.insert(trigger);
    -    const actionId = Actions.insert({
    -      actionType: 'setColor',
    -      selectedColor,
    -      boardId,
    -      desc,
    -    });
    -    Rules.insert({
    -      title: ruleName,
    -      triggerId,
    -      actionId,
    -      boardId,
    -    });
    -  },
    -});
    -
    -Template.setCardActionsColorPopup.onCreated(function () {
    -  this.currentColor = new ReactiveVar(
    -    sharedCardColorButtonValue.get()
    -  );
    -  this.colorButtonValue = sharedCardColorButtonValue;
    -});
    -
    -Template.setCardActionsColorPopup.helpers({
       colors() {
         return cardColors.map(color => ({ color, name: '' }));
       },
     
       isSelected(color) {
    -    return Template.instance().currentColor.get() === color;
    +    return this.currentColor.get() === color;
       },
    -});
     
    -Template.setCardActionsColorPopup.events({
    -  'click .js-palette-color'(event, tpl) {
    -    tpl.currentColor.set(Template.currentData().color);
    +  events() {
    +    return [
    +      {
    +        'click .js-palette-color'() {
    +          this.currentColor.set(this.currentData().color);
    +        },
    +        'click .js-submit'() {
    +          this.colorButtonValue.set(this.currentColor.get());
    +          Popup.back();
    +        },
    +      },
    +    ];
       },
    -  'click .js-submit'(event, tpl) {
    -    tpl.colorButtonValue.set(tpl.currentColor.get());
    -    Popup.back();
    -  },
    -});
    +}).register('setCardActionsColorPopup');
    diff --git a/client/components/rules/actions/checklistActions.jade b/client/components/rules/actions/checklistActions.jade
    index 1795aeac8..399483ec8 100644
    --- a/client/components/rules/actions/checklistActions.jade
    +++ b/client/components/rules/actions/checklistActions.jade
    @@ -5,12 +5,12 @@ template(name="checklistActions")
             select(id="check-action")
               option(value="add") {{_'r-add'}}
               option(value="remove") {{_'r-remove'}}
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-checklist'}}
           div.trigger-dropdown
    -        input(id="checklist-name",type=text,placeholder="{{_'r-name'}}")
    +        input(id="checklist-name",type=text,placeholder="{{_'r-name'}}")  
         div.trigger-button.js-add-checklist-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕  
     
       div.trigger-item
         div.trigger-content
    @@ -18,12 +18,12 @@ template(name="checklistActions")
             select(id="checkall-action")
               option(value="check") {{_'r-check-all'}}
               option(value="uncheck") {{_'r-uncheck-all'}}
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-items-check'}}
           div.trigger-dropdown
    -        input(id="checklist-name2",type=text,placeholder="{{_'r-name'}}")
    +        input(id="checklist-name2",type=text,placeholder="{{_'r-name'}}")  
         div.trigger-button.js-add-checkall-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕
     
     
       div.trigger-item
    @@ -32,32 +32,39 @@ template(name="checklistActions")
             select(id="check-item-action")
               option(value="check") {{_'r-check'}}
               option(value="uncheck") {{_'r-uncheck'}}
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-item'}}
           div.trigger-dropdown
             input(id="checkitem-name",type=text,placeholder="{{_'r-name'}}")
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-of-checklist'}}
           div.trigger-dropdown
    -        input(id="checklist-name3",type=text,placeholder="{{_'r-name'}}")
    +        input(id="checklist-name3",type=text,placeholder="{{_'r-name'}}")  
         div.trigger-button.js-add-check-item-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
           div.trigger-content
    -        div.trigger-text
    +        div.trigger-text 
               | {{_'r-add-checklist'}}
             div.trigger-dropdown
               input(id="checklist-name-3",type=text,placeholder="{{_'r-name'}}")
    -        div.trigger-text
    +        div.trigger-text 
               | {{_'r-with-items'}}
             div.trigger-dropdown
    -          input(id="checklist-items",type=text,placeholder="{{_'r-items-list'}}")
    +          input(id="checklist-items",type=text,placeholder="{{_'r-items-list'}}")    
           div.trigger-button.js-add-checklist-items-action.js-goto-rules
    -        i.fa.fa-plus
    +        | ➕  
     
       div.trigger-item
           div.trigger-content
    -        div.trigger-text
    +        div.trigger-text 
               | {{_'r-checklist-note'}}
     
    +
    +
    +   
    +  
    +
    +
    +
    diff --git a/client/components/rules/actions/checklistActions.js b/client/components/rules/actions/checklistActions.js
    index a38eeb236..64908ab1b 100644
    --- a/client/components/rules/actions/checklistActions.js
    +++ b/client/components/rules/actions/checklistActions.js
    @@ -1,149 +1,150 @@
    -Template.checklistActions.onCreated(function () {
    -  this.subscribe('allRules');
    -});
    -
    -Template.checklistActions.events({
    -  'click .js-add-checklist-items-action'(event, tpl) {
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const checklistName = tpl.find('#checklist-name-3').value;
    -    const checklistItems = tpl.find('#checklist-items').value;
    -    const boardId = Session.get('currentBoard');
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const triggerId = Triggers.insert(trigger);
    -    const actionId = Actions.insert({
    -      actionType: 'addChecklistWithItems',
    -      checklistName,
    -      checklistItems,
    -      boardId,
    -      desc,
    -    });
    -    Rules.insert({
    -      title: ruleName,
    -      triggerId,
    -      actionId,
    -      boardId,
    -    });
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.subscribe('allRules');
       },
    -  'click .js-add-checklist-action'(event, tpl) {
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const actionSelected = tpl.find('#check-action').value;
    -    const checklistName = tpl.find('#checklist-name').value;
    -    const boardId = Session.get('currentBoard');
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    if (actionSelected === 'add') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'addChecklist',
    -        checklistName,
    -        boardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -      });
    -    }
    -    if (actionSelected === 'remove') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'removeChecklist',
    -        checklistName,
    -        boardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -      });
    -    }
    +  events() {
    +    return [
    +      {
    +        'click .js-add-checklist-items-action'(event) {
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const checklistName = this.find('#checklist-name-3').value;
    +          const checklistItems = this.find('#checklist-items').value;
    +          const boardId = Session.get('currentBoard');
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const triggerId = Triggers.insert(trigger);
    +          const actionId = Actions.insert({
    +            actionType: 'addChecklistWithItems',
    +            checklistName,
    +            checklistItems,
    +            boardId,
    +            desc,
    +          });
    +          Rules.insert({
    +            title: ruleName,
    +            triggerId,
    +            actionId,
    +            boardId,
    +          });
    +        },
    +        'click .js-add-checklist-action'(event) {
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const actionSelected = this.find('#check-action').value;
    +          const checklistName = this.find('#checklist-name').value;
    +          const boardId = Session.get('currentBoard');
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          if (actionSelected === 'add') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'addChecklist',
    +              checklistName,
    +              boardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +            });
    +          }
    +          if (actionSelected === 'remove') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'removeChecklist',
    +              checklistName,
    +              boardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +            });
    +          }
    +        },
    +        'click .js-add-checkall-action'(event) {
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const actionSelected = this.find('#checkall-action').value;
    +          const checklistName = this.find('#checklist-name2').value;
    +          const boardId = Session.get('currentBoard');
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          if (actionSelected === 'check') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'checkAll',
    +              checklistName,
    +              boardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +            });
    +          }
    +          if (actionSelected === 'uncheck') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'uncheckAll',
    +              checklistName,
    +              boardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +            });
    +          }
    +        },
    +        'click .js-add-check-item-action'(event) {
    +          const ruleName = this.data().ruleName.get();
    +          const trigger = this.data().triggerVar.get();
    +          const checkItemName = this.find('#checkitem-name');
    +          const checklistName = this.find('#checklist-name3');
    +          const actionSelected = this.find('#check-item-action').value;
    +          const boardId = Session.get('currentBoard');
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          if (actionSelected === 'check') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'checkItem',
    +              checklistName,
    +              checkItemName,
    +              boardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +            });
    +          }
    +          if (actionSelected === 'uncheck') {
    +            const triggerId = Triggers.insert(trigger);
    +            const actionId = Actions.insert({
    +              actionType: 'uncheckItem',
    +              checklistName,
    +              checkItemName,
    +              boardId,
    +              desc,
    +            });
    +            Rules.insert({
    +              title: ruleName,
    +              triggerId,
    +              actionId,
    +              boardId,
    +            });
    +          }
    +        },
    +      },
    +    ];
       },
    -  'click .js-add-checkall-action'(event, tpl) {
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const actionSelected = tpl.find('#checkall-action').value;
    -    const checklistName = tpl.find('#checklist-name2').value;
    -    const boardId = Session.get('currentBoard');
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    if (actionSelected === 'check') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'checkAll',
    -        checklistName,
    -        boardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -      });
    -    }
    -    if (actionSelected === 'uncheck') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'uncheckAll',
    -        checklistName,
    -        boardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -      });
    -    }
    -  },
    -  'click .js-add-check-item-action'(event, tpl) {
    -    const data = Template.currentData();
    -    const ruleName = data.ruleName.get();
    -    const trigger = data.triggerVar.get();
    -    const checkItemName = tpl.find('#checkitem-name');
    -    const checklistName = tpl.find('#checklist-name3');
    -    const actionSelected = tpl.find('#check-item-action').value;
    -    const boardId = Session.get('currentBoard');
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    if (actionSelected === 'check') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'checkItem',
    -        checklistName,
    -        checkItemName,
    -        boardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -      });
    -    }
    -    if (actionSelected === 'uncheck') {
    -      const triggerId = Triggers.insert(trigger);
    -      const actionId = Actions.insert({
    -        actionType: 'uncheckItem',
    -        checklistName,
    -        checkItemName,
    -        boardId,
    -        desc,
    -      });
    -      Rules.insert({
    -        title: ruleName,
    -        triggerId,
    -        actionId,
    -        boardId,
    -      });
    -    }
    -  },
    -});
    +}).register('checklistActions');
    diff --git a/client/components/rules/actions/mailActions.jade b/client/components/rules/actions/mailActions.jade
    index 25e375026..098629e10 100644
    --- a/client/components/rules/actions/mailActions.jade
    +++ b/client/components/rules/actions/mailActions.jade
    @@ -6,6 +6,6 @@ template(name="mailActions")
           div.trigger-dropdown-mail
             input(id="email-to",type=text,placeholder="{{_'r-to'}}")
           input(id="email-subject",type=text,placeholder="{{_'r-subject'}}")
    -      textarea(id="email-msg")
    +      textarea(id="email-msg")  
         div.trigger-button.trigger-button-email.js-mail-action.js-goto-rules
    -      i.fa.fa-plus
    +      | ➕
    diff --git a/client/components/rules/actions/mailActions.js b/client/components/rules/actions/mailActions.js
    index 9e4ee212c..87cb925c1 100644
    --- a/client/components/rules/actions/mailActions.js
    +++ b/client/components/rules/actions/mailActions.js
    @@ -1,27 +1,34 @@
    -Template.mailActions.events({
    -  'click .js-mail-action'(event, tpl) {
    -    const emailTo = tpl.find('#email-to').value;
    -    const emailSubject = tpl.find('#email-subject').value;
    -    const emailMsg = tpl.find('#email-msg').value;
    -    const data = Template.currentData();
    -    const trigger = data.triggerVar.get();
    -    const ruleName = data.ruleName.get();
    -    const triggerId = Triggers.insert(trigger);
    -    const boardId = Session.get('currentBoard');
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const actionId = Actions.insert({
    -      actionType: 'sendEmail',
    -      emailTo,
    -      emailSubject,
    -      emailMsg,
    -      boardId,
    -      desc,
    -    });
    -    Rules.insert({
    -      title: ruleName,
    -      triggerId,
    -      actionId,
    -      boardId,
    -    });
    +BlazeComponent.extendComponent({
    +  onCreated() {},
    +
    +  events() {
    +    return [
    +      {
    +        'click .js-mail-action'(event) {
    +          const emailTo = this.find('#email-to').value;
    +          const emailSubject = this.find('#email-subject').value;
    +          const emailMsg = this.find('#email-msg').value;
    +          const trigger = this.data().triggerVar.get();
    +          const ruleName = this.data().ruleName.get();
    +          const triggerId = Triggers.insert(trigger);
    +          const boardId = Session.get('currentBoard');
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const actionId = Actions.insert({
    +            actionType: 'sendEmail',
    +            emailTo,
    +            emailSubject,
    +            emailMsg,
    +            boardId,
    +            desc,
    +          });
    +          Rules.insert({
    +            title: ruleName,
    +            triggerId,
    +            actionId,
    +            boardId,
    +          });
    +        },
    +      },
    +    ];
       },
    -});
    +}).register('mailActions');
    diff --git a/client/components/rules/ruleDetails.jade b/client/components/rules/ruleDetails.jade
    index f250006d8..d0c10e559 100644
    --- a/client/components/rules/ruleDetails.jade
    +++ b/client/components/rules/ruleDetails.jade
    @@ -1,7 +1,7 @@
     template(name="ruleDetails")
       .rules
         h2
    -      i.fa.fa-magic
    +      | ✨
           | {{_ 'r-rule-details' }}
         .triggers-content
             .triggers-body
    @@ -10,15 +10,15 @@ template(name="ruleDetails")
                       | {{_ 'r-trigger'}}
                     div.trigger-item
                         div.trigger-content
    -                        div.trigger-text
    +                        div.trigger-text 
                             = trigger
    -                h4
    +                h4 
                       | {{_ 'r-action'}}
                     div.trigger-item
                         div.trigger-content
    -                        div.trigger-text
    -                        = action
    +                        div.trigger-text 
    +                        = action 
         div.rules-back
             button.js-goback
    -          i.fa.fa-arrow-left
    +          | ◀️
               | {{_ 'back'}}
    diff --git a/client/components/rules/ruleDetails.js b/client/components/rules/ruleDetails.js
    index c67b9e0b2..235f17179 100644
    --- a/client/components/rules/ruleDetails.js
    +++ b/client/components/rules/ruleDetails.js
    @@ -1,15 +1,14 @@
     import { ReactiveCache } from '/imports/reactiveCache';
     
    -Template.ruleDetails.onCreated(function () {
    -  this.subscribe('allRules');
    -  this.subscribe('allTriggers');
    -  this.subscribe('allActions');
    -  this.subscribe('boards');
    -});
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.subscribe('allRules');
    +    this.subscribe('allTriggers');
    +    this.subscribe('allActions');
    +  },
     
    -Template.ruleDetails.helpers({
       trigger() {
    -    const ruleId = Template.currentData().ruleId;
    +    const ruleId = this.data().ruleId;
         const rule = ReactiveCache.getRule(ruleId.get());
         const trigger = ReactiveCache.getTrigger(rule.triggerId);
         const desc = trigger.description();
    @@ -17,11 +16,15 @@ Template.ruleDetails.helpers({
         return upperdesc;
       },
       action() {
    -    const ruleId = Template.currentData().ruleId;
    +    const ruleId = this.data().ruleId;
         const rule = ReactiveCache.getRule(ruleId.get());
         const action = ReactiveCache.getAction(rule.actionId);
         const desc = action.description();
         const upperdesc = desc.charAt(0).toUpperCase() + desc.substr(1);
         return upperdesc;
       },
    -});
    +
    +  events() {
    +    return [{}];
    +  },
    +}).register('ruleDetails');
    diff --git a/client/components/rules/rulesActions.jade b/client/components/rules/rulesActions.jade
    index 2ffdde5c7..bcc5d7ff0 100644
    --- a/client/components/rules/rulesActions.jade
    +++ b/client/components/rules/rulesActions.jade
    @@ -1,29 +1,29 @@
     template(name="rulesActions")
       h2
    -    i.fa.fa-magic
    -    | {{_ 'r-rule' }} "{{ruleNameStr}}" - {{_ 'r-add-action'}}
    +    | ✨
    +    | {{_ 'r-rule' }} "#{data.ruleName.get}" - {{_ 'r-add-action'}}
       .triggers-content
         .triggers-body
           .triggers-side-menu
             ul
               li.active.js-set-board-actions
    -            i.fa.fa-bar-chart
    +            | 📊
               li.js-set-card-actions
    -            i.fa.fa-file-text-o
    +            | 📝
               li.js-set-checklist-actions
    -            i.fa.fa-check
    +            | ✅
               li.js-set-mail-actions
                 | @
           .triggers-main-body
    -        if $eq currentActions.get 'board'
    +        if ($eq currentActions.get 'board')
               +boardActions(ruleName=data.ruleName triggerVar=data.triggerVar)
    -        else if $eq currentActions.get 'card'
    +        else if ($eq currentActions.get 'card')
               +cardActions(ruleName=data.ruleName triggerVar=data.triggerVar)
    -        else if $eq currentActions.get 'checklist'
    +        else if ($eq currentActions.get 'checklist')
               +checklistActions(ruleName=data.ruleName triggerVar=data.triggerVar)
    -        else if $eq currentActions.get 'mail'
    +        else if ($eq currentActions.get 'mail')
               +mailActions(ruleName=data.ruleName triggerVar=data.triggerVar)
       div.rules-back
             button.js-goback
    -          i.fa.fa-arrow-left
    +          | ◀️
               | {{_ 'back'}}
    diff --git a/client/components/rules/rulesActions.js b/client/components/rules/rulesActions.js
    index 81f7a9574..32b4c3897 100644
    --- a/client/components/rules/rulesActions.js
    +++ b/client/components/rules/rulesActions.js
    @@ -1,25 +1,37 @@
     import { ReactiveCache } from '/imports/reactiveCache';
     
    -Template.rulesActions.onCreated(function () {
    -  this.currentActions = new ReactiveVar('board');
    -});
    -
    -Template.rulesActions.helpers({
    -  currentActions() {
    -    return Template.instance().currentActions;
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.currentActions = new ReactiveVar('board');
       },
     
    -  data() {
    -    return Template.currentData();
    +  setBoardActions() {
    +    this.currentActions.set('board');
    +    $('.js-set-card-actions').removeClass('active');
    +    $('.js-set-board-actions').addClass('active');
    +    $('.js-set-checklist-actions').removeClass('active');
    +    $('.js-set-mail-actions').removeClass('active');
       },
    -
    -  ruleNameStr() {
    -    const rn = Template.currentData() && Template.currentData().ruleName;
    -    try {
    -      return rn && typeof rn.get === 'function' ? rn.get() : '';
    -    } catch (_) {
    -      return '';
    -    }
    +  setCardActions() {
    +    this.currentActions.set('card');
    +    $('.js-set-card-actions').addClass('active');
    +    $('.js-set-board-actions').removeClass('active');
    +    $('.js-set-checklist-actions').removeClass('active');
    +    $('.js-set-mail-actions').removeClass('active');
    +  },
    +  setChecklistActions() {
    +    this.currentActions.set('checklist');
    +    $('.js-set-card-actions').removeClass('active');
    +    $('.js-set-board-actions').removeClass('active');
    +    $('.js-set-checklist-actions').addClass('active');
    +    $('.js-set-mail-actions').removeClass('active');
    +  },
    +  setMailActions() {
    +    this.currentActions.set('mail');
    +    $('.js-set-card-actions').removeClass('active');
    +    $('.js-set-board-actions').removeClass('active');
    +    $('.js-set-checklist-actions').removeClass('active');
    +    $('.js-set-mail-actions').addClass('active');
       },
     
       rules() {
    @@ -28,53 +40,24 @@ Template.rulesActions.helpers({
       },
     
       name() {
    -    // console.log(Template.currentData());
    +    // console.log(this.data());
       },
    -});
    -
    -function setBoardActions(tpl) {
    -  tpl.currentActions.set('board');
    -  $('.js-set-card-actions').removeClass('active');
    -  $('.js-set-board-actions').addClass('active');
    -  $('.js-set-checklist-actions').removeClass('active');
    -  $('.js-set-mail-actions').removeClass('active');
    -}
    -
    -function setCardActions(tpl) {
    -  tpl.currentActions.set('card');
    -  $('.js-set-card-actions').addClass('active');
    -  $('.js-set-board-actions').removeClass('active');
    -  $('.js-set-checklist-actions').removeClass('active');
    -  $('.js-set-mail-actions').removeClass('active');
    -}
    -
    -function setChecklistActions(tpl) {
    -  tpl.currentActions.set('checklist');
    -  $('.js-set-card-actions').removeClass('active');
    -  $('.js-set-board-actions').removeClass('active');
    -  $('.js-set-checklist-actions').addClass('active');
    -  $('.js-set-mail-actions').removeClass('active');
    -}
    -
    -function setMailActions(tpl) {
    -  tpl.currentActions.set('mail');
    -  $('.js-set-card-actions').removeClass('active');
    -  $('.js-set-board-actions').removeClass('active');
    -  $('.js-set-checklist-actions').removeClass('active');
    -  $('.js-set-mail-actions').addClass('active');
    -}
    -
    -Template.rulesActions.events({
    -  'click .js-set-board-actions'(event, tpl) {
    -    setBoardActions(tpl);
    +  events() {
    +    return [
    +      {
    +        'click .js-set-board-actions'() {
    +          this.setBoardActions();
    +        },
    +        'click .js-set-card-actions'() {
    +          this.setCardActions();
    +        },
    +        'click .js-set-mail-actions'() {
    +          this.setMailActions();
    +        },
    +        'click .js-set-checklist-actions'() {
    +          this.setChecklistActions();
    +        },
    +      },
    +    ];
       },
    -  'click .js-set-card-actions'(event, tpl) {
    -    setCardActions(tpl);
    -  },
    -  'click .js-set-mail-actions'(event, tpl) {
    -    setMailActions(tpl);
    -  },
    -  'click .js-set-checklist-actions'(event, tpl) {
    -    setChecklistActions(tpl);
    -  },
    -});
    +}).register('rulesActions');
    diff --git a/client/components/rules/rulesList.jade b/client/components/rules/rulesList.jade
    index 747112b6f..f13255cb1 100644
    --- a/client/components/rules/rulesList.jade
    +++ b/client/components/rules/rulesList.jade
    @@ -1,37 +1,37 @@
     template(name="rulesList")
       .rules
         h2
    -      i.fa.fa-magic
    +      | ✨
           | {{_ 'r-board-rules' }}
     
         ul.rules-list
           each rules
             li.rules-lists-item
    -          p
    +          p 
                 = title
               div.rules-btns-group
                 button.js-goto-details
    -              i.fa.fa-eye
    +              | 👁️
                   | {{_ 'r-view-rule'}}
                 if currentUser.isAdmin
                   button.js-delete-rule
    -                i.fa.fa-trash
    +                | 🗑️
                     | {{_ 'r-delete-rule'}}
                 else if currentUser.isBoardAdmin
                   button.js-delete-rule
    -                i.fa.fa-trash
    +                | 🗑️
                     | {{_ 'r-delete-rule'}}
           else
             li.no-items-message {{_ 'r-no-rules' }}
         if currentUser.isAdmin
           div.rules-add
             button.js-goto-trigger
    -          i.fa.fa-plus
    +          | ➕
               | {{_ 'r-add-rule'}}
             input(type=text,placeholder="{{_ 'r-new-rule-name' }}",id="ruleTitle")
         else if currentUser.isBoardAdmin
           div.rules-add
             button.js-goto-trigger
    -          i.fa.fa-plus
    +          | ➕
               | {{_ 'r-add-rule'}}
             input(type=text,placeholder="{{_ 'r-new-rule-name' }}",id="ruleTitle")
    diff --git a/client/components/rules/rulesList.js b/client/components/rules/rulesList.js
    index d4ca460d6..978e83d2b 100644
    --- a/client/components/rules/rulesList.js
    +++ b/client/components/rules/rulesList.js
    @@ -1,10 +1,10 @@
     import { ReactiveCache } from '/imports/reactiveCache';
     
    -Template.rulesList.onCreated(function () {
    -  this.subscribe('allRules');
    -});
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.subscribe('allRules');
    +  },
     
    -Template.rulesList.helpers({
       rules() {
         const boardId = Session.get('currentBoard');
         const ret = ReactiveCache.getRules({
    @@ -12,4 +12,7 @@ Template.rulesList.helpers({
         });
         return ret;
       },
    -});
    +  events() {
    +    return [{}];
    +  },
    +}).register('rulesList');
    diff --git a/client/components/rules/rulesMain.js b/client/components/rules/rulesMain.js
    index 99d420d8d..ec047b01a 100644
    --- a/client/components/rules/rulesMain.js
    +++ b/client/components/rules/rulesMain.js
    @@ -1,98 +1,103 @@
     import { ReactiveCache } from '/imports/reactiveCache';
     
    -Template.rulesMain.onCreated(function () {
    -  this.rulesCurrentTab = new ReactiveVar('rulesList');
    -  this.ruleName = new ReactiveVar('');
    -  this.triggerVar = new ReactiveVar();
    -  this.ruleId = new ReactiveVar();
    -});
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.rulesCurrentTab = new ReactiveVar('rulesList');
    +    this.ruleName = new ReactiveVar('');
    +    this.triggerVar = new ReactiveVar();
    +    this.ruleId = new ReactiveVar();
    +  },
     
    -Template.rulesMain.helpers({
    -  rulesCurrentTab() {
    -    return Template.instance().rulesCurrentTab;
    +  setTrigger() {
    +    this.rulesCurrentTab.set('trigger');
       },
    -  ruleName() {
    -    return Template.instance().ruleName;
    -  },
    -  triggerVar() {
    -    return Template.instance().triggerVar;
    -  },
    -  ruleId() {
    -    return Template.instance().ruleId;
    -  },
    -});
    -
    -function sanitizeObject(obj) {
    -  Object.keys(obj).forEach(key => {
    -    if (obj[key] === '' || obj[key] === undefined) {
    -      obj[key] = '*';
    -    }
    -  });
    -}
    -
    -Template.rulesMain.events({
    -  'click .js-delete-rule'() {
    -    const rule = Template.currentData();
    -    Rules.remove(rule._id);
    -    Actions.remove(rule.actionId);
    -    Triggers.remove(rule.triggerId);
    -  },
    -  'click .js-goto-trigger'(event, tpl) {
    -    event.preventDefault();
    -    const ruleTitle = tpl.find('#ruleTitle').value;
    -    if (ruleTitle !== undefined && ruleTitle !== '') {
    -      tpl.find('#ruleTitle').value = '';
    -      tpl.ruleName.set(ruleTitle);
    -      tpl.rulesCurrentTab.set('trigger');
    -    }
    -  },
    -  'click .js-goto-action'(event, tpl) {
    -    event.preventDefault();
    -    // Add user to the trigger
    -    const username = $(event.currentTarget.offsetParent)
    -      .find('.user-name')
    -      .val();
    -    let trigger = tpl.triggerVar.get();
    -    trigger.userId = '*';
    -    if (username !== undefined) {
    -      const userFound = ReactiveCache.getUser({ username });
    -      if (userFound !== undefined) {
    -        trigger.userId = userFound._id;
    -        tpl.triggerVar.set(trigger);
    +  sanitizeObject(obj) {
    +    Object.keys(obj).forEach(key => {
    +      if (obj[key] === '' || obj[key] === undefined) {
    +        obj[key] = '*';
           }
    -    }
    -    // Sanitize trigger
    -    trigger = tpl.triggerVar.get();
    -    sanitizeObject(trigger);
    -    tpl.triggerVar.set(trigger);
    -    tpl.rulesCurrentTab.set('action');
    +    });
       },
    -  'click .js-show-user-field'(event) {
    -    event.preventDefault();
    -    $(event.currentTarget.offsetParent)
    -      .find('.user-details')
    -      .removeClass('hide-element');
    +  setRulesList() {
    +    this.rulesCurrentTab.set('rulesList');
       },
    -  'click .js-goto-rules'(event, tpl) {
    -    event.preventDefault();
    -    tpl.rulesCurrentTab.set('rulesList');
    +
    +  setAction() {
    +    this.rulesCurrentTab.set('action');
       },
    -  'click .js-goback'(event, tpl) {
    -    event.preventDefault();
    -    if (
    -      tpl.rulesCurrentTab.get() === 'trigger' ||
    -      tpl.rulesCurrentTab.get() === 'ruleDetails'
    -    ) {
    -      tpl.rulesCurrentTab.set('rulesList');
    -    }
    -    if (tpl.rulesCurrentTab.get() === 'action') {
    -      tpl.rulesCurrentTab.set('trigger');
    -    }
    +
    +  setRuleDetails() {
    +    this.rulesCurrentTab.set('ruleDetails');
       },
    -  'click .js-goto-details'(event, tpl) {
    -    event.preventDefault();
    -    const rule = Template.currentData();
    -    tpl.ruleId.set(rule._id);
    -    tpl.rulesCurrentTab.set('ruleDetails');
    +
    +  events() {
    +    return [
    +      {
    +        'click .js-delete-rule'() {
    +          const rule = this.currentData();
    +          Rules.remove(rule._id);
    +          Actions.remove(rule.actionId);
    +          Triggers.remove(rule.triggerId);
    +        },
    +        'click .js-goto-trigger'(event) {
    +          event.preventDefault();
    +          const ruleTitle = this.find('#ruleTitle').value;
    +          if (ruleTitle !== undefined && ruleTitle !== '') {
    +            this.find('#ruleTitle').value = '';
    +            this.ruleName.set(ruleTitle);
    +            this.setTrigger();
    +          }
    +        },
    +        'click .js-goto-action'(event) {
    +          event.preventDefault();
    +          // Add user to the trigger
    +          const username = $(event.currentTarget.offsetParent)
    +            .find('.user-name')
    +            .val();
    +          let trigger = this.triggerVar.get();
    +          trigger.userId = '*';
    +          if (username !== undefined) {
    +            const userFound = ReactiveCache.getUser({ username });
    +            if (userFound !== undefined) {
    +              trigger.userId = userFound._id;
    +              this.triggerVar.set(trigger);
    +            }
    +          }
    +          // Sanitize trigger
    +          trigger = this.triggerVar.get();
    +          this.sanitizeObject(trigger);
    +          this.triggerVar.set(trigger);
    +          this.setAction();
    +        },
    +        'click .js-show-user-field'(event) {
    +          event.preventDefault();
    +          $(event.currentTarget.offsetParent)
    +            .find('.user-details')
    +            .removeClass('hide-element');
    +        },
    +        'click .js-goto-rules'(event) {
    +          event.preventDefault();
    +          this.setRulesList();
    +        },
    +        'click .js-goback'(event) {
    +          event.preventDefault();
    +          if (
    +            this.rulesCurrentTab.get() === 'trigger' ||
    +            this.rulesCurrentTab.get() === 'ruleDetails'
    +          ) {
    +            this.setRulesList();
    +          }
    +          if (this.rulesCurrentTab.get() === 'action') {
    +            this.setTrigger();
    +          }
    +        },
    +        'click .js-goto-details'(event) {
    +          event.preventDefault();
    +          const rule = this.currentData();
    +          this.ruleId.set(rule._id);
    +          this.setRuleDetails();
    +        },
    +      },
    +    ];
       },
    -});
    +}).register('rulesMain');
    diff --git a/client/components/rules/rulesTriggers.jade b/client/components/rules/rulesTriggers.jade
    index bc13496f8..e34c1dfb5 100644
    --- a/client/components/rules/rulesTriggers.jade
    +++ b/client/components/rules/rulesTriggers.jade
    @@ -1,17 +1,17 @@
     template(name="rulesTriggers")
       h2
    -    i.fa.fa-magic
    -    | {{_ 'r-rule' }} "{{ruleNameStr}}" - {{_ 'r-add-trigger'}}
    +    | ✨
    +    | {{_ 'r-rule' }} "#{data.ruleName.get}" - {{_ 'r-add-trigger'}}
       .triggers-content
         .triggers-body
           .triggers-side-menu
             ul
               li.active.js-set-board-triggers
    -            i.fa.fa-bar-chart
    +            | 📊
               li.js-set-card-triggers
    -            i.fa.fa-file-text-o
    +            | 📝
               li.js-set-checklist-triggers
    -            i.fa.fa-check
    +            | ✅
           .triggers-main-body
             if showBoardTrigger.get
               +boardTriggers
    @@ -21,5 +21,5 @@ template(name="rulesTriggers")
               +checklistTriggers
       div.rules-back
             button.js-goback
    -          i.fa.fa-arrow-left
    +          | ◀️
               | {{_ 'back'}}
    diff --git a/client/components/rules/rulesTriggers.js b/client/components/rules/rulesTriggers.js
    index ce2bb1328..960484267 100644
    --- a/client/components/rules/rulesTriggers.js
    +++ b/client/components/rules/rulesTriggers.js
    @@ -1,31 +1,35 @@
     import { ReactiveCache } from '/imports/reactiveCache';
     
    -Template.rulesTriggers.onCreated(function () {
    -  this.showBoardTrigger = new ReactiveVar(true);
    -  this.showCardTrigger = new ReactiveVar(false);
    -  this.showChecklistTrigger = new ReactiveVar(false);
    -});
    -
    -Template.rulesTriggers.helpers({
    -  ruleNameStr() {
    -    const rn = Template.currentData() && Template.currentData().ruleName;
    -    try {
    -      return rn && typeof rn.get === 'function' ? rn.get() : '';
    -    } catch (_) {
    -      return '';
    -    }
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.showBoardTrigger = new ReactiveVar(true);
    +    this.showCardTrigger = new ReactiveVar(false);
    +    this.showChecklistTrigger = new ReactiveVar(false);
       },
     
    -  showBoardTrigger() {
    -    return Template.instance().showBoardTrigger;
    +  setBoardTriggers() {
    +    this.showBoardTrigger.set(true);
    +    this.showCardTrigger.set(false);
    +    this.showChecklistTrigger.set(false);
    +    $('.js-set-card-triggers').removeClass('active');
    +    $('.js-set-board-triggers').addClass('active');
    +    $('.js-set-checklist-triggers').removeClass('active');
       },
    -
    -  showCardTrigger() {
    -    return Template.instance().showCardTrigger;
    +  setCardTriggers() {
    +    this.showBoardTrigger.set(false);
    +    this.showCardTrigger.set(true);
    +    this.showChecklistTrigger.set(false);
    +    $('.js-set-card-triggers').addClass('active');
    +    $('.js-set-board-triggers').removeClass('active');
    +    $('.js-set-checklist-triggers').removeClass('active');
       },
    -
    -  showChecklistTrigger() {
    -    return Template.instance().showChecklistTrigger;
    +  setChecklistTriggers() {
    +    this.showBoardTrigger.set(false);
    +    this.showCardTrigger.set(false);
    +    this.showChecklistTrigger.set(true);
    +    $('.js-set-card-triggers').removeClass('active');
    +    $('.js-set-board-triggers').removeClass('active');
    +    $('.js-set-checklist-triggers').addClass('active');
       },
     
       rules() {
    @@ -34,45 +38,21 @@ Template.rulesTriggers.helpers({
       },
     
       name() {
    -    // console.log(Template.currentData());
    +    // console.log(this.data());
       },
    -});
    -
    -function setBoardTriggers(tpl) {
    -  tpl.showBoardTrigger.set(true);
    -  tpl.showCardTrigger.set(false);
    -  tpl.showChecklistTrigger.set(false);
    -  $('.js-set-card-triggers').removeClass('active');
    -  $('.js-set-board-triggers').addClass('active');
    -  $('.js-set-checklist-triggers').removeClass('active');
    -}
    -
    -function setCardTriggers(tpl) {
    -  tpl.showBoardTrigger.set(false);
    -  tpl.showCardTrigger.set(true);
    -  tpl.showChecklistTrigger.set(false);
    -  $('.js-set-card-triggers').addClass('active');
    -  $('.js-set-board-triggers').removeClass('active');
    -  $('.js-set-checklist-triggers').removeClass('active');
    -}
    -
    -function setChecklistTriggers(tpl) {
    -  tpl.showBoardTrigger.set(false);
    -  tpl.showCardTrigger.set(false);
    -  tpl.showChecklistTrigger.set(true);
    -  $('.js-set-card-triggers').removeClass('active');
    -  $('.js-set-board-triggers').removeClass('active');
    -  $('.js-set-checklist-triggers').addClass('active');
    -}
    -
    -Template.rulesTriggers.events({
    -  'click .js-set-board-triggers'(event, tpl) {
    -    setBoardTriggers(tpl);
    +  events() {
    +    return [
    +      {
    +        'click .js-set-board-triggers'() {
    +          this.setBoardTriggers();
    +        },
    +        'click .js-set-card-triggers'() {
    +          this.setCardTriggers();
    +        },
    +        'click .js-set-checklist-triggers'() {
    +          this.setChecklistTriggers();
    +        },
    +      },
    +    ];
       },
    -  'click .js-set-card-triggers'(event, tpl) {
    -    setCardTriggers(tpl);
    -  },
    -  'click .js-set-checklist-triggers'(event, tpl) {
    -    setChecklistTriggers(tpl);
    -  },
    -});
    +}).register('rulesTriggers');
    diff --git a/client/components/rules/triggers/boardTriggers.jade b/client/components/rules/triggers/boardTriggers.jade
    index 54c0693d4..d9d2c4e17 100644
    --- a/client/components/rules/triggers/boardTriggers.jade
    +++ b/client/components/rules/triggers/boardTriggers.jade
    @@ -1,111 +1,111 @@
     template(name="boardTriggers")
       div.trigger-item#trigger-two
         div.trigger-content
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-when-a-card'}}
    -      div.trigger-inline-button.js-open-card-title-popup
    -        i.fa.fa-search
    -      div.trigger-text
    +      div.trigger-inline-button.js-open-card-title-popup 
    +        | 🔍
    +      div.trigger-text 
             | {{_'r-is'}}
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-added-to'}}
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-list'}}
           div.trigger-dropdown
             input(id="create-list-name",type=text,placeholder="{{_'r-list-name'}}")
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-in-swimlane'}}
           div.trigger-dropdown
    -        input(id="create-swimlane-name",type=text,placeholder="{{_'r-swimlane-name'}}")
    +        input(id="create-swimlane-name",type=text,placeholder="{{_'r-swimlane-name'}}") 
           div.trigger-button.trigger-button-person.js-show-user-field
    -        i.fa.fa-user
    +        | 👤
           div.user-details.hide-element
             div.trigger-text
               | {{_'r-by'}}
             div.trigger-dropdown
               input(class="user-name",type=text,placeholder="{{_'username'}}")
         div.trigger-button.js-add-create-trigger.js-goto-action
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item#trigger-three
         div.trigger-content
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-when-a-card'}}
    -      div.trigger-inline-button.js-open-card-title-popup
    -        i.fa.fa-search
    -      div.trigger-text
    +      div.trigger-inline-button.js-open-card-title-popup 
    +        | 🔍
    +      div.trigger-text 
             | {{_'r-is-moved'}}
           div.trigger-button.trigger-button-person.js-show-user-field
    -        i.fa.fa-user
    +        | 👤
           div.user-details.hide-element
             div.trigger-text
               | {{_'r-by'}}
             div.trigger-dropdown
               input(class="user-name",type=text,placeholder="{{_'username'}}")
         div.trigger-button.js-add-gen-moved-trigger.js-goto-action
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item#trigger-four
         div.trigger-content
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-when-a-card'}}
    -      div.trigger-inline-button.js-open-card-title-popup
    -        i.fa.fa-search
    -      div.trigger-text
    +      div.trigger-inline-button.js-open-card-title-popup 
    +        | 🔍
    +      div.trigger-text 
             | {{_'r-is'}}
           div.trigger-dropdown
             select(id="move-action")
               option(value="moved-to") {{_'r-moved-to'}}
               option(value="moved-from") {{_'r-moved-from'}}
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-list'}}
           div.trigger-dropdown
             input(id="move-list-name",type=text,placeholder="{{_'r-list-name'}}")
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-in-swimlane'}}
           div.trigger-dropdown
    -        input(id="create-swimlane-name-2",type=text,placeholder="{{_'r-swimlane-name'}}")
    +        input(id="create-swimlane-name-2",type=text,placeholder="{{_'r-swimlane-name'}}") 
           div.trigger-button.trigger-button-person.js-show-user-field
    -        i.fa.fa-user
    +        | 👤
           div.user-details.hide-element
             div.trigger-text
               | {{_'r-by'}}
             div.trigger-dropdown
               input(class="user-name",type=text,placeholder="{{_'username'}}")
         div.trigger-button.js-add-moved-trigger.js-goto-action
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item#trigger-five
         div.trigger-content
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-when-a-card'}}
    -      div.trigger-inline-button.js-open-card-title-popup
    -        i.fa.fa-search
    -      div.trigger-text
    +      div.trigger-inline-button.js-open-card-title-popup 
    +        | 🔍
    +      div.trigger-text 
             | {{_'r-is'}}
           div.trigger-dropdown
             select(id="arch-action")
               option(value="archived") {{_'r-archived'}}
               option(value="unarchived") {{_'r-unarchived'}}
           div.trigger-button.trigger-button-person.js-show-user-field
    -        i.fa.fa-user
    +        | 👤
           div.user-details.hide-element
             div.trigger-text
               | {{_'r-by'}}
             div.trigger-dropdown
               input(class="user-name",type=text,placeholder="{{_'username'}}")
         div.trigger-button.js-add-arch-trigger.js-goto-action
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
           div.trigger-content
    -        div.trigger-text
    +        div.trigger-text 
               | {{_'r-board-note'}}
     
     template(name="boardCardTitlePopup")
         form
           label
    -        | {{_ 'boardCardTitlePopup-title'}}
    +        | Card Title Filter
             input.js-card-filter-name(type="text" value=title autofocus)
           input.js-card-filter-button.primary.wide(type="submit" value="{{_ 'set-filter'}}")
     
    diff --git a/client/components/rules/triggers/boardTriggers.js b/client/components/rules/triggers/boardTriggers.js
    index db5193b4a..77e0c620e 100644
    --- a/client/components/rules/triggers/boardTriggers.js
    +++ b/client/components/rules/triggers/boardTriggers.js
    @@ -1,115 +1,120 @@
    -Template.boardTriggers.onCreated(function () {
    -  this.provaVar = new ReactiveVar('');
    -  this.currentPopupTriggerId = 'def';
    -  this.cardTitleFilters = {};
    -  this.setNameFilter = (name) => {
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.provaVar = new ReactiveVar('');
    +    this.currentPopupTriggerId = 'def';
    +    this.cardTitleFilters = {};
    +  },
    +  setNameFilter(name) {
         this.cardTitleFilters[this.currentPopupTriggerId] = name;
    -  };
    -});
    +  },
     
    -Template.boardTriggers.events({
    -  'click .js-open-card-title-popup'(event, tpl) {
    -    const funct = Popup.open('boardCardTitle');
    -    const divId = $(event.currentTarget.parentNode.parentNode).attr('id');
    -    tpl.currentPopupTriggerId = divId;
    -    funct.call(this, event);
    -  },
    -  'click .js-add-create-trigger'(event, tpl) {
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const datas = Template.currentData();
    -    const listName = tpl.find('#create-list-name').value;
    -    const swimlaneName = tpl.find('#create-swimlane-name').value;
    -    const boardId = Session.get('currentBoard');
    -    const divId = $(event.currentTarget.parentNode).attr('id');
    -    const cardTitle = tpl.cardTitleFilters[divId];
    -    // move to generic funciont
    -    datas.triggerVar.set({
    -      activityType: 'createCard',
    -      boardId,
    -      cardTitle,
    -      swimlaneName,
    -      listName,
    -      desc,
    -    });
    -  },
    -  'click .js-add-moved-trigger'(event, tpl) {
    -    const datas = Template.currentData();
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const swimlaneName = tpl.find('#create-swimlane-name-2').value;
    -    const actionSelected = tpl.find('#move-action').value;
    -    const listName = tpl.find('#move-list-name').value;
    -    const boardId = Session.get('currentBoard');
    -    const divId = $(event.currentTarget.parentNode).attr('id');
    -    const cardTitle = tpl.cardTitleFilters[divId];
    -    if (actionSelected === 'moved-to') {
    -      datas.triggerVar.set({
    -        activityType: 'moveCard',
    -        boardId,
    -        listName,
    -        cardTitle,
    -        swimlaneName,
    -        oldListName: '*',
    -        desc,
    -      });
    -    }
    -    if (actionSelected === 'moved-from') {
    -      datas.triggerVar.set({
    -        activityType: 'moveCard',
    -        boardId,
    -        cardTitle,
    -        swimlaneName,
    -        listName: '*',
    -        oldListName: listName,
    -        desc,
    -      });
    -    }
    -  },
    -  'click .js-add-gen-moved-trigger'(event, tpl) {
    -    const datas = Template.currentData();
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const boardId = Session.get('currentBoard');
    +  events() {
    +    return [
    +      {
    +        'click .js-open-card-title-popup'(event) {
    +          const funct = Popup.open('boardCardTitle');
    +          const divId = $(event.currentTarget.parentNode.parentNode).attr('id');
    +          //console.log('current popup');
    +          //console.log(this.currentPopupTriggerId);
    +          this.currentPopupTriggerId = divId;
    +          funct.call(this, event);
    +        },
    +        'click .js-add-create-trigger'(event) {
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const datas = this.data();
    +          const listName = this.find('#create-list-name').value;
    +          const swimlaneName = this.find('#create-swimlane-name').value;
    +          const boardId = Session.get('currentBoard');
    +          const divId = $(event.currentTarget.parentNode).attr('id');
    +          const cardTitle = this.cardTitleFilters[divId];
    +          // move to generic funciont
    +          datas.triggerVar.set({
    +            activityType: 'createCard',
    +            boardId,
    +            cardTitle,
    +            swimlaneName,
    +            listName,
    +            desc,
    +          });
    +        },
    +        'click .js-add-moved-trigger'(event) {
    +          const datas = this.data();
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const swimlaneName = this.find('#create-swimlane-name-2').value;
    +          const actionSelected = this.find('#move-action').value;
    +          const listName = this.find('#move-list-name').value;
    +          const boardId = Session.get('currentBoard');
    +          const divId = $(event.currentTarget.parentNode).attr('id');
    +          const cardTitle = this.cardTitleFilters[divId];
    +          if (actionSelected === 'moved-to') {
    +            datas.triggerVar.set({
    +              activityType: 'moveCard',
    +              boardId,
    +              listName,
    +              cardTitle,
    +              swimlaneName,
    +              oldListName: '*',
    +              desc,
    +            });
    +          }
    +          if (actionSelected === 'moved-from') {
    +            datas.triggerVar.set({
    +              activityType: 'moveCard',
    +              boardId,
    +              cardTitle,
    +              swimlaneName,
    +              listName: '*',
    +              oldListName: listName,
    +              desc,
    +            });
    +          }
    +        },
    +        'click .js-add-gen-moved-trigger'(event) {
    +          const datas = this.data();
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const boardId = Session.get('currentBoard');
     
    -    datas.triggerVar.set({
    -      activityType: 'moveCard',
    -      boardId,
    -      swimlaneName: '*',
    -      listName: '*',
    -      oldListName: '*',
    -      desc,
    -    });
    +          datas.triggerVar.set({
    +            activityType: 'moveCard',
    +            boardId,
    +            swimlaneName: '*',
    +            listName: '*',
    +            oldListName: '*',
    +            desc,
    +          });
    +        },
    +        'click .js-add-arc-trigger'(event) {
    +          const datas = this.data();
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const actionSelected = this.find('#arch-action').value;
    +          const boardId = Session.get('currentBoard');
    +          if (actionSelected === 'archived') {
    +            datas.triggerVar.set({
    +              activityType: 'archivedCard',
    +              boardId,
    +              desc,
    +            });
    +          }
    +          if (actionSelected === 'unarchived') {
    +            datas.triggerVar.set({
    +              activityType: 'restoredCard',
    +              boardId,
    +              desc,
    +            });
    +          }
    +        },
    +      },
    +    ];
       },
    -  'click .js-add-arc-trigger'(event, tpl) {
    -    const datas = Template.currentData();
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const actionSelected = tpl.find('#arch-action').value;
    -    const boardId = Session.get('currentBoard');
    -    if (actionSelected === 'archived') {
    -      datas.triggerVar.set({
    -        activityType: 'archivedCard',
    -        boardId,
    -        desc,
    -      });
    -    }
    -    if (actionSelected === 'unarchived') {
    -      datas.triggerVar.set({
    -        activityType: 'restoredCard',
    -        boardId,
    -        desc,
    -      });
    -    }
    -  },
    -});
    +}).register('boardTriggers');
     
     Template.boardCardTitlePopup.events({
    -  submit(event) {
    -    const title = $(event.target)
    -      .find('.js-card-filter-name')
    +  submit(event, templateInstance) {
    +    const title = templateInstance
    +      .$('.js-card-filter-name')
           .val()
           .trim();
    -    const opener = Popup.getOpenerComponent();
    -    if (opener?.setNameFilter) {
    -      opener.setNameFilter(title);
    -    }
    +    Popup.getOpenerComponent().setNameFilter(title);
         event.preventDefault();
         Popup.back();
       },
    diff --git a/client/components/rules/triggers/cardTriggers.jade b/client/components/rules/triggers/cardTriggers.jade
    index ba4276a51..60c024452 100644
    --- a/client/components/rules/triggers/cardTriggers.jade
    +++ b/client/components/rules/triggers/cardTriggers.jade
    @@ -10,14 +10,14 @@ template(name="cardTriggers")
           div.trigger-text
             | {{_'r-a-card'}}
           div.trigger-button.trigger-button-person.js-show-user-field
    -        i.fa.fa-user
    +        | 👤
           div.user-details.hide-element
             div.trigger-text
               | {{_'r-by'}}
             div.trigger-dropdown
               input(class="user-name",type=text,placeholder="{{_'username'}}")
         div.trigger-button.js-add-gen-label-trigger.js-goto-action
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    @@ -37,14 +37,14 @@ template(name="cardTriggers")
           div.trigger-text
             | {{_'r-a-card'}}
           div.trigger-button.trigger-button-person.js-show-user-field
    -        i.fa.fa-user
    +        | 👤
           div.user-details.hide-element
             div.trigger-text
               | {{_'r-by'}}
             div.trigger-dropdown
               input(class="user-name",type=text,placeholder="{{_'username'}}")
         div.trigger-button.js-add-spec-label-trigger.js-goto-action
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    @@ -57,14 +57,14 @@ template(name="cardTriggers")
           div.trigger-text
             | {{_'r-a-card'}}
           div.trigger-button.trigger-button-person.js-show-user-field
    -        i.fa.fa-user
    +        | 👤
           div.user-details.hide-element
             div.trigger-text
               | {{_'r-by'}}
             div.trigger-dropdown
               input(class="user-name",type=text,placeholder="{{_'username'}}")
         div.trigger-button.js-add-gen-member-trigger.js-goto-action
    -      i.fa.fa-plus
    +      | ➕
     
     
       div.trigger-item
    @@ -82,14 +82,14 @@ template(name="cardTriggers")
           div.trigger-text
             | {{_'r-a-card'}}
           div.trigger-button.trigger-button-person.js-show-user-field
    -        i.fa.fa-user
    +        | 👤
           div.user-details.hide-element
             div.trigger-text
               | {{_'r-by'}}
             div.trigger-dropdown
               input(class="user-name",type=text,placeholder="{{_'username'}}")
         div.trigger-button.js-add-spec-member-trigger.js-goto-action
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    @@ -104,11 +104,11 @@ template(name="cardTriggers")
           div.trigger-text
             | {{_'r-a-card'}}
           div.trigger-button.trigger-button-person.js-show-user-field
    -        i.fa.fa-user
    +        | 👤
           div.user-details.hide-element
             div.trigger-text
               | {{_'r-by'}}
             div.trigger-dropdown
               input(class="user-name",type=text,placeholder="{{_'username'}}")
         div.trigger-button.js-add-attachment-trigger.js-goto-action
    -      i.fa.fa-plus
    +      | ➕
    diff --git a/client/components/rules/triggers/cardTriggers.js b/client/components/rules/triggers/cardTriggers.js
    index 5ef7546ef..120ee074c 100644
    --- a/client/components/rules/triggers/cardTriggers.js
    +++ b/client/components/rules/triggers/cardTriggers.js
    @@ -1,10 +1,9 @@
     import { TAPi18n } from '/imports/i18n';
     
    -Template.cardTriggers.onCreated(function () {
    -  this.subscribe('allRules');
    -});
    -
    -Template.cardTriggers.helpers({
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.subscribe('allRules');
    +  },
       labels() {
         const labels = Utils.getCurrentBoard().labels;
         for (let i = 0; i < labels.length; i++) {
    @@ -17,117 +16,120 @@ Template.cardTriggers.helpers({
         }
         return labels;
       },
    -});
    -
    -Template.cardTriggers.events({
    -  'click .js-add-gen-label-trigger'(event, tpl) {
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const datas = Template.currentData();
    -    const actionSelected = tpl.find('#label-action').value;
    -    const boardId = Session.get('currentBoard');
    -    if (actionSelected === 'added') {
    -      datas.triggerVar.set({
    -        activityType: 'addedLabel',
    -        boardId,
    -        labelId: '*',
    -        desc,
    -      });
    -    }
    -    if (actionSelected === 'removed') {
    -      datas.triggerVar.set({
    -        activityType: 'removedLabel',
    -        boardId,
    -        labelId: '*',
    -        desc,
    -      });
    -    }
    +  events() {
    +    return [
    +      {
    +        'click .js-add-gen-label-trigger'(event) {
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const datas = this.data();
    +          const actionSelected = this.find('#label-action').value;
    +          const boardId = Session.get('currentBoard');
    +          if (actionSelected === 'added') {
    +            datas.triggerVar.set({
    +              activityType: 'addedLabel',
    +              boardId,
    +              labelId: '*',
    +              desc,
    +            });
    +          }
    +          if (actionSelected === 'removed') {
    +            datas.triggerVar.set({
    +              activityType: 'removedLabel',
    +              boardId,
    +              labelId: '*',
    +              desc,
    +            });
    +          }
    +        },
    +        'click .js-add-spec-label-trigger'(event) {
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const datas = this.data();
    +          const actionSelected = this.find('#spec-label-action').value;
    +          const labelId = this.find('#spec-label').value;
    +          const boardId = Session.get('currentBoard');
    +          if (actionSelected === 'added') {
    +            datas.triggerVar.set({
    +              activityType: 'addedLabel',
    +              boardId,
    +              labelId,
    +              desc,
    +            });
    +          }
    +          if (actionSelected === 'removed') {
    +            datas.triggerVar.set({
    +              activityType: 'removedLabel',
    +              boardId,
    +              labelId,
    +              desc,
    +            });
    +          }
    +        },
    +        'click .js-add-gen-member-trigger'(event) {
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const datas = this.data();
    +          const actionSelected = this.find('#gen-member-action').value;
    +          const boardId = Session.get('currentBoard');
    +          if (actionSelected === 'added') {
    +            datas.triggerVar.set({
    +              activityType: 'joinMember',
    +              boardId,
    +              username: '*',
    +              desc,
    +            });
    +          }
    +          if (actionSelected === 'removed') {
    +            datas.triggerVar.set({
    +              activityType: 'unjoinMember',
    +              boardId,
    +              username: '*',
    +              desc,
    +            });
    +          }
    +        },
    +        'click .js-add-spec-member-trigger'(event) {
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const datas = this.data();
    +          const actionSelected = this.find('#spec-member-action').value;
    +          const username = this.find('#spec-member').value;
    +          const boardId = Session.get('currentBoard');
    +          if (actionSelected === 'added') {
    +            datas.triggerVar.set({
    +              activityType: 'joinMember',
    +              boardId,
    +              username,
    +              desc,
    +            });
    +          }
    +          if (actionSelected === 'removed') {
    +            datas.triggerVar.set({
    +              activityType: 'unjoinMember',
    +              boardId,
    +              username,
    +              desc,
    +            });
    +          }
    +        },
    +        'click .js-add-attachment-trigger'(event) {
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const datas = this.data();
    +          const actionSelected = this.find('#attach-action').value;
    +          const boardId = Session.get('currentBoard');
    +          if (actionSelected === 'added') {
    +            datas.triggerVar.set({
    +              activityType: 'addAttachment',
    +              boardId,
    +              desc,
    +            });
    +          }
    +          if (actionSelected === 'removed') {
    +            datas.triggerVar.set({
    +              activityType: 'deleteAttachment',
    +              boardId,
    +              desc,
    +            });
    +          }
    +        },
    +      },
    +    ];
       },
    -  'click .js-add-spec-label-trigger'(event, tpl) {
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const datas = Template.currentData();
    -    const actionSelected = tpl.find('#spec-label-action').value;
    -    const labelId = tpl.find('#spec-label').value;
    -    const boardId = Session.get('currentBoard');
    -    if (actionSelected === 'added') {
    -      datas.triggerVar.set({
    -        activityType: 'addedLabel',
    -        boardId,
    -        labelId,
    -        desc,
    -      });
    -    }
    -    if (actionSelected === 'removed') {
    -      datas.triggerVar.set({
    -        activityType: 'removedLabel',
    -        boardId,
    -        labelId,
    -        desc,
    -      });
    -    }
    -  },
    -  'click .js-add-gen-member-trigger'(event, tpl) {
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const datas = Template.currentData();
    -    const actionSelected = tpl.find('#gen-member-action').value;
    -    const boardId = Session.get('currentBoard');
    -    if (actionSelected === 'added') {
    -      datas.triggerVar.set({
    -        activityType: 'joinMember',
    -        boardId,
    -        username: '*',
    -        desc,
    -      });
    -    }
    -    if (actionSelected === 'removed') {
    -      datas.triggerVar.set({
    -        activityType: 'unjoinMember',
    -        boardId,
    -        username: '*',
    -        desc,
    -      });
    -    }
    -  },
    -  'click .js-add-spec-member-trigger'(event, tpl) {
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const datas = Template.currentData();
    -    const actionSelected = tpl.find('#spec-member-action').value;
    -    const username = tpl.find('#spec-member').value;
    -    const boardId = Session.get('currentBoard');
    -    if (actionSelected === 'added') {
    -      datas.triggerVar.set({
    -        activityType: 'joinMember',
    -        boardId,
    -        username,
    -        desc,
    -      });
    -    }
    -    if (actionSelected === 'removed') {
    -      datas.triggerVar.set({
    -        activityType: 'unjoinMember',
    -        boardId,
    -        username,
    -        desc,
    -      });
    -    }
    -  },
    -  'click .js-add-attachment-trigger'(event, tpl) {
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const datas = Template.currentData();
    -    const actionSelected = tpl.find('#attach-action').value;
    -    const boardId = Session.get('currentBoard');
    -    if (actionSelected === 'added') {
    -      datas.triggerVar.set({
    -        activityType: 'addAttachment',
    -        boardId,
    -        desc,
    -      });
    -    }
    -    if (actionSelected === 'removed') {
    -      datas.triggerVar.set({
    -        activityType: 'deleteAttachment',
    -        boardId,
    -        desc,
    -      });
    -    }
    -  },
    -});
    +}).register('cardTriggers');
    diff --git a/client/components/rules/triggers/checklistTriggers.jade b/client/components/rules/triggers/checklistTriggers.jade
    index e60687f2c..b6c16f1de 100644
    --- a/client/components/rules/triggers/checklistTriggers.jade
    +++ b/client/components/rules/triggers/checklistTriggers.jade
    @@ -1,125 +1,125 @@
     template(name="checklistTriggers")
       div.trigger-item
         div.trigger-content
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-when-a-checklist'}}
           div.trigger-dropdown
             select(id="gen-check-action")
               option(value="created") {{_'r-added-to'}}
               option(value="removed") {{_'r-removed-from'}}
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-a-card'}}
           div.trigger-button.trigger-button-person.js-show-user-field
    -        i.fa.fa-user
    +        | 👤
           div.user-details.hide-element
             div.trigger-text
               | {{_'r-by'}}
             div.trigger-dropdown
               input(class="user-name",type=text,placeholder="{{_'username'}}")
         div.trigger-button.js-add-gen-check-trigger.js-goto-action
    -      i.fa.fa-plus
    +      | ➕
     
     
       div.trigger-item
         div.trigger-content
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-when-the-checklist'}}
           div.trigger-dropdown
    -        input(id="check-name",type=text,placeholder="{{_'r-name'}}")
    -      div.trigger-text
    +        input(id="check-name",type=text,placeholder="{{_'r-name'}}") 
    +      div.trigger-text 
             | {{_'r-is'}}
           div.trigger-dropdown
             select(id="spec-check-action")
               option(value="created") {{_'r-added-to'}}
               option(value="removed") {{_'r-removed-from'}}
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-a-card'}}
           div.trigger-button.trigger-button-person.js-show-user-field
    -        i.fa.fa-user
    +        | 👤
           div.user-details.hide-element
             div.trigger-text
               | {{_'r-by'}}
             div.trigger-dropdown
               input(class="user-name",type=text,placeholder="{{_'username'}}")
         div.trigger-button.js-add-spec-check-trigger.js-goto-action
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-when-a-checklist'}}
           div.trigger-dropdown
             select(id="gen-comp-check-action")
               option(value="completed") {{_'r-completed'}}
               option(value="uncompleted") {{_'r-made-incomplete'}}
           div.trigger-button.trigger-button-person.js-show-user-field
    -        i.fa.fa-user
    +        | 👤
           div.user-details.hide-element
             div.trigger-text
               | {{_'r-by'}}
             div.trigger-dropdown
               input(class="user-name",type=text,placeholder="{{_'username'}}")
         div.trigger-button.js-add-gen-comp-trigger.js-goto-action
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-when-the-checklist'}}
           div.trigger-dropdown
    -        input(id="spec-comp-check-name",type=text,placeholder="{{_'r-name'}}")
    -      div.trigger-text
    +        input(id="spec-comp-check-name",type=text,placeholder="{{_'r-name'}}") 
    +      div.trigger-text 
             | {{_'r-is'}}
           div.trigger-dropdown
             select(id="spec-comp-check-action")
               option(value="completed") {{_'r-completed'}}
               option(value="uncompleted") {{_'r-made-incomplete'}}
           div.trigger-button.trigger-button-person.js-show-user-field
    -        i.fa.fa-user
    +        | 👤
           div.user-details.hide-element
             div.trigger-text
               | {{_'r-by'}}
             div.trigger-dropdown
               input(class="user-name",type=text,placeholder="{{_'username'}}")
         div.trigger-button.js-add-spec-comp-trigger.js-goto-action
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-when-a-item'}}
           div.trigger-dropdown
             select(id="check-item-gen-action")
               option(value="checked") {{_'r-checked'}}
               option(value="unchecked") {{_'r-unchecked'}}
           div.trigger-button.trigger-button-person.js-show-user-field
    -        i.fa.fa-user
    +        | 👤
           div.user-details.hide-element
             div.trigger-text
               | {{_'r-by'}}
             div.trigger-dropdown
               input(class="user-name",type=text,placeholder="{{_'username'}}")
         div.trigger-button.js-add-gen-check-item-trigger.js-goto-action
    -      i.fa.fa-plus
    +      | ➕
     
       div.trigger-item
         div.trigger-content
    -      div.trigger-text
    +      div.trigger-text 
             | {{_'r-when-the-item'}}
           div.trigger-dropdown
    -        input(id="check-item-name",type=text,placeholder="{{_'r-name'}}")
    -      div.trigger-text
    +        input(id="check-item-name",type=text,placeholder="{{_'r-name'}}") 
    +      div.trigger-text 
             | {{_'r-is'}}
           div.trigger-dropdown
             select(id="check-item-spec-action")
               option(value="checked") {{_'r-checked'}}
               option(value="unchecked") {{_'r-unchecked'}}
           div.trigger-button.trigger-button-person.js-show-user-field
    -        i.fa.fa-user
    +        | 👤
           div.user-details.hide-element
             div.trigger-text
               | {{_'r-by'}}
             div.trigger-dropdown
               input(class="user-name",type=text,placeholder="{{_'username'}}")
         div.trigger-button.js-add-spec-check-item-trigger.js-goto-action
    -      i.fa.fa-plus
    +      | ➕
    diff --git a/client/components/rules/triggers/checklistTriggers.js b/client/components/rules/triggers/checklistTriggers.js
    index d50a6d7b8..9b2cd99c2 100644
    --- a/client/components/rules/triggers/checklistTriggers.js
    +++ b/client/components/rules/triggers/checklistTriggers.js
    @@ -1,142 +1,147 @@
    -Template.checklistTriggers.onCreated(function () {
    -  this.subscribe('allRules');
    -});
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.subscribe('allRules');
    +  },
    +  events() {
    +    return [
    +      {
    +        'click .js-add-gen-check-trigger'(event) {
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const datas = this.data();
    +          const actionSelected = this.find('#gen-check-action').value;
    +          const boardId = Session.get('currentBoard');
    +          if (actionSelected === 'created') {
    +            datas.triggerVar.set({
    +              activityType: 'addChecklist',
    +              boardId,
    +              checklistName: '*',
    +              desc,
    +            });
    +          }
    +          if (actionSelected === 'removed') {
    +            datas.triggerVar.set({
    +              activityType: 'removeChecklist',
    +              boardId,
    +              checklistName: '*',
    +              desc,
    +            });
    +          }
    +        },
    +        'click .js-add-spec-check-trigger'(event) {
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const datas = this.data();
    +          const actionSelected = this.find('#spec-check-action').value;
    +          const checklistId = this.find('#check-name').value;
    +          const boardId = Session.get('currentBoard');
    +          if (actionSelected === 'created') {
    +            datas.triggerVar.set({
    +              activityType: 'addChecklist',
    +              boardId,
    +              checklistName: checklistId,
    +              desc,
    +            });
    +          }
    +          if (actionSelected === 'removed') {
    +            datas.triggerVar.set({
    +              activityType: 'removeChecklist',
    +              boardId,
    +              checklistName: checklistId,
    +              desc,
    +            });
    +          }
    +        },
    +        'click .js-add-gen-comp-trigger'(event) {
    +          const desc = Utils.getTriggerActionDesc(event, this);
     
    -Template.checklistTriggers.events({
    -  'click .js-add-gen-check-trigger'(event, tpl) {
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const datas = Template.currentData();
    -    const actionSelected = tpl.find('#gen-check-action').value;
    -    const boardId = Session.get('currentBoard');
    -    if (actionSelected === 'created') {
    -      datas.triggerVar.set({
    -        activityType: 'addChecklist',
    -        boardId,
    -        checklistName: '*',
    -        desc,
    -      });
    -    }
    -    if (actionSelected === 'removed') {
    -      datas.triggerVar.set({
    -        activityType: 'removeChecklist',
    -        boardId,
    -        checklistName: '*',
    -        desc,
    -      });
    -    }
    +          const datas = this.data();
    +          const actionSelected = this.find('#gen-comp-check-action').value;
    +          const boardId = Session.get('currentBoard');
    +          if (actionSelected === 'completed') {
    +            datas.triggerVar.set({
    +              activityType: 'completeChecklist',
    +              boardId,
    +              checklistName: '*',
    +              desc,
    +            });
    +          }
    +          if (actionSelected === 'uncompleted') {
    +            datas.triggerVar.set({
    +              activityType: 'uncompleteChecklist',
    +              boardId,
    +              checklistName: '*',
    +              desc,
    +            });
    +          }
    +        },
    +        'click .js-add-spec-comp-trigger'(event) {
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const datas = this.data();
    +          const actionSelected = this.find('#spec-comp-check-action').value;
    +          const checklistId = this.find('#spec-comp-check-name').value;
    +          const boardId = Session.get('currentBoard');
    +          if (actionSelected === 'completed') {
    +            datas.triggerVar.set({
    +              activityType: 'completeChecklist',
    +              boardId,
    +              checklistName: checklistId,
    +              desc,
    +            });
    +          }
    +          if (actionSelected === 'uncompleted') {
    +            datas.triggerVar.set({
    +              activityType: 'uncompleteChecklist',
    +              boardId,
    +              checklistName: checklistId,
    +              desc,
    +            });
    +          }
    +        },
    +        'click .js-add-gen-check-item-trigger'(event) {
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const datas = this.data();
    +          const actionSelected = this.find('#check-item-gen-action').value;
    +          const boardId = Session.get('currentBoard');
    +          if (actionSelected === 'checked') {
    +            datas.triggerVar.set({
    +              activityType: 'checkedItem',
    +              boardId,
    +              checklistItemName: '*',
    +              desc,
    +            });
    +          }
    +          if (actionSelected === 'unchecked') {
    +            datas.triggerVar.set({
    +              activityType: 'uncheckedItem',
    +              boardId,
    +              checklistItemName: '*',
    +              desc,
    +            });
    +          }
    +        },
    +        'click .js-add-spec-check-item-trigger'(event) {
    +          const desc = Utils.getTriggerActionDesc(event, this);
    +          const datas = this.data();
    +          const actionSelected = this.find('#check-item-spec-action').value;
    +          const checklistItemId = this.find('#check-item-name').value;
    +          const boardId = Session.get('currentBoard');
    +          if (actionSelected === 'checked') {
    +            datas.triggerVar.set({
    +              activityType: 'checkedItem',
    +              boardId,
    +              checklistItemName: checklistItemId,
    +              desc,
    +            });
    +          }
    +          if (actionSelected === 'unchecked') {
    +            datas.triggerVar.set({
    +              activityType: 'uncheckedItem',
    +              boardId,
    +              checklistItemName: checklistItemId,
    +              desc,
    +            });
    +          }
    +        },
    +      },
    +    ];
       },
    -  'click .js-add-spec-check-trigger'(event, tpl) {
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const datas = Template.currentData();
    -    const actionSelected = tpl.find('#spec-check-action').value;
    -    const checklistId = tpl.find('#check-name').value;
    -    const boardId = Session.get('currentBoard');
    -    if (actionSelected === 'created') {
    -      datas.triggerVar.set({
    -        activityType: 'addChecklist',
    -        boardId,
    -        checklistName: checklistId,
    -        desc,
    -      });
    -    }
    -    if (actionSelected === 'removed') {
    -      datas.triggerVar.set({
    -        activityType: 'removeChecklist',
    -        boardId,
    -        checklistName: checklistId,
    -        desc,
    -      });
    -    }
    -  },
    -  'click .js-add-gen-comp-trigger'(event, tpl) {
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -
    -    const datas = Template.currentData();
    -    const actionSelected = tpl.find('#gen-comp-check-action').value;
    -    const boardId = Session.get('currentBoard');
    -    if (actionSelected === 'completed') {
    -      datas.triggerVar.set({
    -        activityType: 'completeChecklist',
    -        boardId,
    -        checklistName: '*',
    -        desc,
    -      });
    -    }
    -    if (actionSelected === 'uncompleted') {
    -      datas.triggerVar.set({
    -        activityType: 'uncompleteChecklist',
    -        boardId,
    -        checklistName: '*',
    -        desc,
    -      });
    -    }
    -  },
    -  'click .js-add-spec-comp-trigger'(event, tpl) {
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const datas = Template.currentData();
    -    const actionSelected = tpl.find('#spec-comp-check-action').value;
    -    const checklistId = tpl.find('#spec-comp-check-name').value;
    -    const boardId = Session.get('currentBoard');
    -    if (actionSelected === 'completed') {
    -      datas.triggerVar.set({
    -        activityType: 'completeChecklist',
    -        boardId,
    -        checklistName: checklistId,
    -        desc,
    -      });
    -    }
    -    if (actionSelected === 'uncompleted') {
    -      datas.triggerVar.set({
    -        activityType: 'uncompleteChecklist',
    -        boardId,
    -        checklistName: checklistId,
    -        desc,
    -      });
    -    }
    -  },
    -  'click .js-add-gen-check-item-trigger'(event, tpl) {
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const datas = Template.currentData();
    -    const actionSelected = tpl.find('#check-item-gen-action').value;
    -    const boardId = Session.get('currentBoard');
    -    if (actionSelected === 'checked') {
    -      datas.triggerVar.set({
    -        activityType: 'checkedItem',
    -        boardId,
    -        checklistItemName: '*',
    -        desc,
    -      });
    -    }
    -    if (actionSelected === 'unchecked') {
    -      datas.triggerVar.set({
    -        activityType: 'uncheckedItem',
    -        boardId,
    -        checklistItemName: '*',
    -        desc,
    -      });
    -    }
    -  },
    -  'click .js-add-spec-check-item-trigger'(event, tpl) {
    -    const desc = Utils.getTriggerActionDesc(event, tpl);
    -    const datas = Template.currentData();
    -    const actionSelected = tpl.find('#check-item-spec-action').value;
    -    const checklistItemId = tpl.find('#check-item-name').value;
    -    const boardId = Session.get('currentBoard');
    -    if (actionSelected === 'checked') {
    -      datas.triggerVar.set({
    -        activityType: 'checkedItem',
    -        boardId,
    -        checklistItemName: checklistItemId,
    -        desc,
    -      });
    -    }
    -    if (actionSelected === 'unchecked') {
    -      datas.triggerVar.set({
    -        activityType: 'uncheckedItem',
    -        boardId,
    -        checklistItemName: checklistItemId,
    -        desc,
    -      });
    -    }
    -  },
    -});
    +}).register('checklistTriggers');
    diff --git a/client/components/settings/adminReports.jade b/client/components/settings/adminReports.jade
    index c6dc7feb1..e474ee465 100644
    --- a/client/components/settings/adminReports.jade
    +++ b/client/components/settings/adminReports.jade
    @@ -8,27 +8,27 @@ template(name="adminReports")
               ul
                 li
                   a.js-report-broken(data-id="report-broken")
    -                span.emoji-icon 🔗
    +                | 🔗
                     | {{_ 'broken-cards'}}
     
                 li
                   a.js-report-files(data-id="report-files")
    -                span.emoji-icon 📎
    +                | 📎
                     | {{_ 'filesReportTitle'}}
     
                 li
                   a.js-report-rules(data-id="report-rules")
    -                span.emoji-icon ✨
    +                | ✨
                     | {{_ 'rulesReportTitle'}}
     
                 li
                   a.js-report-boards(data-id="report-boards")
    -                span.emoji-icon ✨
    +                | ✨
                     | {{_ 'boardsReportTitle'}}
     
                 li
                   a.js-report-cards(data-id="report-cards")
    -                span.emoji-icon ✨
    +                | ✨
                     | {{_ 'cardsReportTitle'}}
     
             .main-body
    diff --git a/client/components/settings/adminReports.js b/client/components/settings/adminReports.js
    index 40cff8aeb..23a438347 100644
    --- a/client/components/settings/adminReports.js
    +++ b/client/components/settings/adminReports.js
    @@ -1,178 +1,143 @@
     import { ReactiveCache } from '/imports/reactiveCache';
     import { TAPi18n } from '/imports/i18n';
     import { AttachmentStorage } from '/models/attachments';
    -import { CardSearchPaged } from '/client/lib/cardSearch';
    +import { CardSearchPagedComponent } from '/client/lib/cardSearch';
     import SessionData from '/models/usersessiondata';
     import { QueryParams } from '/config/query-classes';
     import { OPERATOR_LIMIT } from '/config/search-const';
     const filesize = require('filesize');
     
    -// --- Shared helper functions (formerly AdminReport base class methods) ---
    +BlazeComponent.extendComponent({
    +  subscription: null,
    +  showFilesReport: new ReactiveVar(false),
    +  showBrokenCardsReport: new ReactiveVar(false),
    +  showOrphanedFilesReport: new ReactiveVar(false),
    +  showRulesReport: new ReactiveVar(false),
    +  showCardsReport: new ReactiveVar(false),
    +  showBoardsReport: new ReactiveVar(false),
    +  sessionId: null,
     
    -function yesOrNo(value) {
    -  if (value) {
    -    return TAPi18n.__('yes');
    -  } else {
    -    return TAPi18n.__('no');
    -  }
    -}
    +  onCreated() {
    +    this.error = new ReactiveVar('');
    +    this.loading = new ReactiveVar(false);
    +    this.sessionId = SessionData.getSessionId();
    +  },
     
    -function fileSizeHelper(size) {
    -  let ret = "";
    -  if (_.isNumber(size)) {
    -    ret = filesize(size);
    -  }
    -  return ret;
    -}
    +  events() {
    +    return [
    +      {
    +        'click a.js-report-broken': this.switchMenu,
    +        'click a.js-report-files': this.switchMenu,
    +        'click a.js-report-rules': this.switchMenu,
    +        'click a.js-report-cards': this.switchMenu,
    +        'click a.js-report-boards': this.switchMenu,
    +      },
    +    ];
    +  },
     
    -function abbreviate(text) {
    -  if (text?.length > 30) {
    -    return `${text.substr(0, 29)}...`;
    -  }
    -  return text;
    -}
    +  switchMenu(event) {
    +    const target = $(event.target);
    +    if (!target.hasClass('active')) {
    +      this.loading.set(true);
    +      this.showFilesReport.set(false);
    +      this.showBrokenCardsReport.set(false);
    +      this.showOrphanedFilesReport.set(false);
    +      this.showRulesReport.set(false)
    +      this.showBoardsReport.set(false);
    +      this.showCardsReport.set(false);
    +      if (this.subscription) {
    +        this.subscription.stop();
    +      }
     
    -function collectionResults(collection) {
    -  return collection.find();
    -}
    +      $('.side-menu li.active').removeClass('active');
    +      target.parent().addClass('active');
    +      const targetID = target.data('id');
     
    -function collectionResultsCount(collection) {
    -  return collection.find().count();
    -}
    -
    -// --- adminReports template ---
    -
    -Template.adminReports.onCreated(function () {
    -  this.subscription = null;
    -  this.showFilesReport = new ReactiveVar(false);
    -  this.showBrokenCardsReport = new ReactiveVar(false);
    -  this.showOrphanedFilesReport = new ReactiveVar(false);
    -  this.showRulesReport = new ReactiveVar(false);
    -  this.showCardsReport = new ReactiveVar(false);
    -  this.showBoardsReport = new ReactiveVar(false);
    -  this.sessionId = SessionData.getSessionId();
    -  this.error = new ReactiveVar('');
    -  this.loading = new ReactiveVar(false);
    -});
    -
    -Template.adminReports.helpers({
    -  showFilesReport() {
    -    return Template.instance().showFilesReport;
    -  },
    -  showBrokenCardsReport() {
    -    return Template.instance().showBrokenCardsReport;
    -  },
    -  showOrphanedFilesReport() {
    -    return Template.instance().showOrphanedFilesReport;
    -  },
    -  showRulesReport() {
    -    return Template.instance().showRulesReport;
    -  },
    -  showCardsReport() {
    -    return Template.instance().showCardsReport;
    -  },
    -  showBoardsReport() {
    -    return Template.instance().showBoardsReport;
    -  },
    -  loading() {
    -    return Template.instance().loading;
    -  },
    -});
    -
    -Template.adminReports.events({
    -  'click a.js-report-broken'(event) {
    -    switchMenu(event, Template.instance());
    -  },
    -  'click a.js-report-files'(event) {
    -    switchMenu(event, Template.instance());
    -  },
    -  'click a.js-report-rules'(event) {
    -    switchMenu(event, Template.instance());
    -  },
    -  'click a.js-report-cards'(event) {
    -    switchMenu(event, Template.instance());
    -  },
    -  'click a.js-report-boards'(event) {
    -    switchMenu(event, Template.instance());
    -  },
    -});
    -
    -function switchMenu(event, tmpl) {
    -  const target = $(event.target);
    -  if (!target.hasClass('active')) {
    -    tmpl.loading.set(true);
    -    tmpl.showFilesReport.set(false);
    -    tmpl.showBrokenCardsReport.set(false);
    -    tmpl.showOrphanedFilesReport.set(false);
    -    tmpl.showRulesReport.set(false);
    -    tmpl.showBoardsReport.set(false);
    -    tmpl.showCardsReport.set(false);
    -    if (tmpl.subscription) {
    -      tmpl.subscription.stop();
    +      if ('report-broken' === targetID) {
    +        this.showBrokenCardsReport.set(true);
    +        this.subscription = Meteor.subscribe(
    +          'brokenCards',
    +          SessionData.getSessionId(),
    +          () => {
    +            this.loading.set(false);
    +          },
    +        );
    +      } else if ('report-files' === targetID) {
    +        this.showFilesReport.set(true);
    +        this.subscription = Meteor.subscribe('attachmentsList', () => {
    +          this.loading.set(false);
    +        });
    +      } else if ('report-rules' === targetID) {
    +        this.subscription = Meteor.subscribe('rulesReport', () => {
    +          this.showRulesReport.set(true);
    +          this.loading.set(false);
    +        });
    +      } else if ('report-boards' === targetID) {
    +        this.subscription = Meteor.subscribe('boardsReport', () => {
    +          this.showBoardsReport.set(true);
    +          this.loading.set(false);
    +        });
    +      } else if ('report-cards' === targetID) {
    +        const qp = new QueryParams();
    +        qp.addPredicate(OPERATOR_LIMIT, 300);
    +        this.subscription = Meteor.subscribe(
    +          'globalSearch',
    +          this.sessionId,
    +          qp.getParams(),
    +          qp.text,
    +          () => {
    +            this.showCardsReport.set(true);
    +            this.loading.set(false);
    +          },
    +        );
    +      }
         }
    +  },
    +}).register('adminReports');
     
    -    $('.side-menu li.active').removeClass('active');
    -    target.parent().addClass('active');
    -    const targetID = target.data('id');
    +class AdminReport extends BlazeComponent {
    +  collection;
     
    -    if ('report-broken' === targetID) {
    -      tmpl.showBrokenCardsReport.set(true);
    -      tmpl.subscription = Meteor.subscribe(
    -        'brokenCards',
    -        SessionData.getSessionId(),
    -        () => {
    -          tmpl.loading.set(false);
    -        },
    -      );
    -    } else if ('report-files' === targetID) {
    -      tmpl.showFilesReport.set(true);
    -      tmpl.subscription = Meteor.subscribe('attachmentsList', () => {
    -        tmpl.loading.set(false);
    -      });
    -    } else if ('report-rules' === targetID) {
    -      tmpl.subscription = Meteor.subscribe('rulesReport', () => {
    -        tmpl.showRulesReport.set(true);
    -        tmpl.loading.set(false);
    -      });
    -    } else if ('report-boards' === targetID) {
    -      tmpl.subscription = Meteor.subscribe('boardsReport', () => {
    -        tmpl.showBoardsReport.set(true);
    -        tmpl.loading.set(false);
    -      });
    -    } else if ('report-cards' === targetID) {
    -      const qp = new QueryParams();
    -      qp.addPredicate(OPERATOR_LIMIT, 300);
    -      tmpl.subscription = Meteor.subscribe(
    -        'globalSearch',
    -        tmpl.sessionId,
    -        qp.getParams(),
    -        qp.text,
    -        () => {
    -          tmpl.showCardsReport.set(true);
    -          tmpl.loading.set(false);
    -        },
    -      );
    -    }
    -  }
    -}
    -
    -// --- filesReport template ---
    -
    -Template.filesReport.helpers({
       results() {
    -    return collectionResults(Attachments);
    -  },
    +    // eslint-disable-next-line no-console
    +    return this.collection.find();
    +  }
    +
    +  yesOrNo(value) {
    +    if (value) {
    +      return TAPi18n.__('yes');
    +    } else {
    +      return TAPi18n.__('no');
    +    }
    +  }
    +
       resultsCount() {
    -    return collectionResultsCount(Attachments);
    -  },
    +    return this.collection.find().count();
    +  }
    +
       fileSize(size) {
    -    return fileSizeHelper(size);
    -  },
    -});
    +    let ret = "";
    +    if (_.isNumber(size)) {
    +      ret = filesize(size);
    +    }
    +    return ret;
    +  }
     
    -// --- rulesReport template ---
    +  abbreviate(text) {
    +    if (text?.length > 30) {
    +      return `${text.substr(0, 29)}...`;
    +    }
    +    return text;
    +  }
    +}
    +
    +(class extends AdminReport {
    +  collection = Attachments;
    +}.register('filesReport'));
    +
    +(class extends AdminReport {
    +  collection = Rules;
     
    -Template.rulesReport.helpers({
       results() {
         const rules = [];
     
    @@ -190,27 +155,12 @@ Template.rulesReport.helpers({
         // eslint-disable-next-line no-console
         console.log('rows:', rules);
         return rules;
    -  },
    -  resultsCount() {
    -    return collectionResultsCount(Rules);
    -  },
    -});
    +  }
    +}.register('rulesReport'));
     
    -// --- boardsReport template ---
    +(class extends AdminReport {
    +  collection = Boards;
     
    -Template.boardsReport.helpers({
    -  results() {
    -    return collectionResults(Boards);
    -  },
    -  resultsCount() {
    -    return collectionResultsCount(Boards);
    -  },
    -  yesOrNo(value) {
    -    return yesOrNo(value);
    -  },
    -  abbreviate(text) {
    -    return abbreviate(text);
    -  },
       userNames(members) {
         const ret = (members || [])
           .map(_member => {
    @@ -219,7 +169,7 @@ Template.boardsReport.helpers({
           })
           .join(", ");
         return ret;
    -  },
    +  }
       teams(memberTeams) {
         const ret = (memberTeams || [])
           .map(_memberTeam => {
    @@ -228,7 +178,7 @@ Template.boardsReport.helpers({
           })
           .join(", ");
         return ret;
    -  },
    +  }
       orgs(orgs) {
         const ret = (orgs || [])
           .map(_orgs => {
    @@ -237,21 +187,13 @@ Template.boardsReport.helpers({
           })
           .join(", ");
         return ret;
    -  },
    -});
    +  }
    +}.register('boardsReport'));
     
    -// --- cardsReport template ---
     
    -Template.cardsReport.helpers({
    -  results() {
    -    return collectionResults(Cards);
    -  },
    -  resultsCount() {
    -    return collectionResultsCount(Cards);
    -  },
    -  abbreviate(text) {
    -    return abbreviate(text);
    -  },
    +(class extends AdminReport {
    +  collection = Cards;
    +
       userNames(userIds) {
         const ret = (userIds || [])
           .map(_userId => {
    @@ -259,45 +201,13 @@ Template.cardsReport.helpers({
             return _ret;
           })
           .join(", ");
    -    return ret;
    -  },
    -});
    +    return ret
    +  }
    +}.register('cardsReport'));
     
    -// --- brokenCardsReport template ---
    -
    -Template.brokenCardsReport.onCreated(function () {
    -  const search = new CardSearchPaged(this);
    -  this.search = search;
    -});
    -
    -Template.brokenCardsReport.helpers({
    -  resultsCount() {
    -    return Template.instance().search.resultsCount;
    -  },
    -  resultsHeading() {
    -    return Template.instance().search.resultsHeading;
    -  },
    -  results() {
    -    return Template.instance().search.results;
    -  },
    -  getSearchHref() {
    -    return Template.instance().search.getSearchHref();
    -  },
    -  hasPreviousPage() {
    -    return Template.instance().search.hasPreviousPage;
    -  },
    -  hasNextPage() {
    -    return Template.instance().search.hasNextPage;
    -  },
    -});
    -
    -Template.brokenCardsReport.events({
    -  'click .js-next-page'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.search.nextPage();
    -  },
    -  'click .js-previous-page'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.search.previousPage();
    -  },
    -});
    +class BrokenCardsComponent extends CardSearchPagedComponent {
    +  onCreated() {
    +    super.onCreated();
    +  }
    +}
    +BrokenCardsComponent.register('brokenCardsReport');
    diff --git a/client/components/settings/attachmentSettings.jade b/client/components/settings/attachmentSettings.jade
    index 4ff9cc487..669f84131 100644
    --- a/client/components/settings/attachmentSettings.jade
    +++ b/client/components/settings/attachmentSettings.jade
    @@ -6,12 +6,12 @@ template(name="attachmentSettings")
             label {{_ 'writable-path'}}
             input.wekan-form-control#filesystem-path(type="text" value="{{filesystemPath}}" readonly)
             small.form-text.text-muted {{_ 'filesystem-path-description'}}
    -
    +      
           .form-group
             label {{_ 'attachments-path'}}
             input.wekan-form-control#attachments-path(type="text" value="{{attachmentsPath}}" readonly)
             small.form-text.text-muted {{_ 'attachments-path-description'}}
    -
    +      
           .form-group
             label {{_ 'avatars-path'}}
             input.wekan-form-control#avatars-path(type="text" value="{{avatarsPath}}" readonly)
    @@ -30,42 +30,42 @@ template(name="attachmentSettings")
             label {{_ 's3-enabled'}}
             input.wekan-form-control#s3-enabled(type="checkbox" checked="{{s3Enabled}}" disabled)
             small.form-text.text-muted {{_ 's3-enabled-description'}}
    -
    +      
           .form-group
             label {{_ 's3-endpoint'}}
             input.wekan-form-control#s3-endpoint(type="text" value="{{s3Endpoint}}" readonly)
             small.form-text.text-muted {{_ 's3-endpoint-description'}}
    -
    +      
           .form-group
             label {{_ 's3-bucket'}}
             input.wekan-form-control#s3-bucket(type="text" value="{{s3Bucket}}" readonly)
             small.form-text.text-muted {{_ 's3-bucket-description'}}
    -
    +      
           .form-group
             label {{_ 's3-region'}}
             input.wekan-form-control#s3-region(type="text" value="{{s3Region}}" readonly)
             small.form-text.text-muted {{_ 's3-region-description'}}
    -
    +      
           .form-group
             label {{_ 's3-access-key'}}
             input.wekan-form-control#s3-access-key(type="text" placeholder="{{_ 's3-access-key-placeholder'}}" readonly)
             small.form-text.text-muted {{_ 's3-access-key-description'}}
    -
    +      
           .form-group
             label {{_ 's3-secret-key'}}
             input.wekan-form-control#s3-secret-key(type="password" placeholder="{{_ 's3-secret-key-placeholder'}}")
             small.form-text.text-muted {{_ 's3-secret-key-description'}}
    -
    +      
           .form-group
             label {{_ 's3-ssl-enabled'}}
             input.wekan-form-control#s3-ssl-enabled(type="checkbox" checked="{{s3SslEnabled}}" disabled)
             small.form-text.text-muted {{_ 's3-ssl-enabled-description'}}
    -
    +      
           .form-group
             label {{_ 's3-port'}}
             input.wekan-form-control#s3-port(type="number" value="{{s3Port}}" readonly)
             small.form-text.text-muted {{_ 's3-port-description'}}
    -
    +      
           .form-group
             button.js-test-s3-connection.btn.btn-secondary {{_ 'test-s3-connection'}}
             button.js-save-s3-settings.btn.btn-primary {{_ 'save-s3-settings'}}
    @@ -73,19 +73,19 @@ template(name="attachmentSettings")
     template(name="storageSettings")
       .storage-settings
         h3 {{_ 'attachment-storage-configuration'}}
    -
    +    
         .storage-config-section
           h4 {{_ 'filesystem-storage'}}
           .form-group
             label {{_ 'writable-path'}}
             input.wekan-form-control#filesystem-path(type="text" value="{{filesystemPath}}" readonly)
             small.form-text.text-muted {{_ 'filesystem-path-description'}}
    -
    +      
           .form-group
             label {{_ 'attachments-path'}}
             input.wekan-form-control#attachments-path(type="text" value="{{attachmentsPath}}" readonly)
             small.form-text.text-muted {{_ 'attachments-path-description'}}
    -
    +      
           .form-group
             label {{_ 'avatars-path'}}
             input.wekan-form-control#avatars-path(type="text" value="{{avatarsPath}}" readonly)
    @@ -104,37 +104,37 @@ template(name="storageSettings")
             label {{_ 's3-enabled'}}
             input.wekan-form-control#s3-enabled(type="checkbox" checked="{{s3Enabled}}" disabled)
             small.form-text.text-muted {{_ 's3-enabled-description'}}
    -
    +      
           .form-group
             label {{_ 's3-endpoint'}}
             input.wekan-form-control#s3-endpoint(type="text" value="{{s3Endpoint}}" readonly)
             small.form-text.text-muted {{_ 's3-endpoint-description'}}
    -
    +      
           .form-group
             label {{_ 's3-bucket'}}
             input.wekan-form-control#s3-bucket(type="text" value="{{s3Bucket}}" readonly)
             small.form-text.text-muted {{_ 's3-bucket-description'}}
    -
    +      
           .form-group
             label {{_ 's3-region'}}
             input.wekan-form-control#s3-region(type="text" value="{{s3Region}}" readonly)
             small.form-text.text-muted {{_ 's3-region-description'}}
    -
    +      
           .form-group
             label {{_ 's3-access-key'}}
             input.wekan-form-control#s3-access-key(type="text" placeholder="{{_ 's3-access-key-placeholder'}}" readonly)
             small.form-text.text-muted {{_ 's3-access-key-description'}}
    -
    +      
           .form-group
             label {{_ 's3-secret-key'}}
             input.wekan-form-control#s3-secret-key(type="password" placeholder="{{_ 's3-secret-key-placeholder'}}")
             small.form-text.text-muted {{_ 's3-secret-key-description'}}
    -
    +      
           .form-group
             label {{_ 's3-ssl-enabled'}}
             input.wekan-form-control#s3-ssl-enabled(type="checkbox" checked="{{s3SslEnabled}}" disabled)
             small.form-text.text-muted {{_ 's3-ssl-enabled-description'}}
    -
    +      
           .form-group
             label {{_ 's3-port'}}
             input.wekan-form-control#s3-port(type="number" value="{{s3Port}}" readonly)
    @@ -147,18 +147,18 @@ template(name="storageSettings")
     template(name="attachmentMigration")
       .attachment-migration
         h3 {{_ 'attachment-migration'}}
    -
    +    
         .migration-controls
           .form-group
             label {{_ 'migration-batch-size'}}
             input.wekan-form-control#migration-batch-size(type="number" value="{{migrationBatchSize}}" min="1" max="100")
             small.form-text.text-muted {{_ 'migration-batch-size-description'}}
    -
    +      
           .form-group
             label {{_ 'migration-delay-ms'}}
             input.wekan-form-control#migration-delay-ms(type="number" value="{{migrationDelayMs}}" min="100" max="10000")
             small.form-text.text-muted {{_ 'migration-delay-ms-description'}}
    -
    +      
           .form-group
             label {{_ 'migration-cpu-threshold'}}
             input.wekan-form-control#migration-cpu-threshold(type="number" value="{{migrationCpuThreshold}}" min="10" max="90")
    @@ -169,7 +169,7 @@ template(name="attachmentMigration")
             button.js-migrate-all-to-filesystem.btn.btn-primary {{_ 'migrate-all-to-filesystem'}}
             button.js-migrate-all-to-gridfs.btn.btn-primary {{_ 'migrate-all-to-gridfs'}}
             button.js-migrate-all-to-s3.btn.btn-primary {{_ 'migrate-all-to-s3'}}
    -
    +      
           .migration-controls
             button.js-pause-migration.btn.btn-warning {{_ 'pause-migration'}}
             button.js-resume-migration.btn.btn-success {{_ 'resume-migration'}}
    @@ -180,7 +180,7 @@ template(name="attachmentMigration")
           .progress
             .progress-bar(role="progressbar" style="width: {{migrationProgress}}%" aria-valuenow="{{migrationProgress}}" aria-valuemin="0" aria-valuemax="100")
               | {{migrationProgress}}%
    -
    +      
           .migration-stats
             .stat-item
               span.label {{_ 'total-attachments'}}:
    @@ -203,7 +203,7 @@ template(name="attachmentMigration")
     template(name="attachmentMonitoring")
       .attachment-monitoring
         h3 {{_ 'attachment-monitoring'}}
    -
    +    
         .monitoring-stats
           .stats-grid
             .stat-card
    diff --git a/client/components/settings/attachments.jade b/client/components/settings/attachments.jade
    index 31a0e219f..111bd4db8 100644
    --- a/client/components/settings/attachments.jade
    +++ b/client/components/settings/attachments.jade
    @@ -8,7 +8,7 @@ template(name="attachments")
               ul
                 li
                   a.js-move-attachments(data-id="move-attachments")
    -                i.fa.fa-arrow-right
    +                | ➡️
                     | {{_ 'attachment-move'}}
     
             .main-body
    @@ -80,17 +80,17 @@ template(name="moveAttachment")
           td
             if $neq version.storageName "fs"
               button.js-move-storage-fs
    -            i.fa.fa-arrow-right
    +            | ➡️
                 | {{_ 'attachment-move-storage-fs'}}
     
             if $neq version.storageName "gridfs"
               if version.storageName
                 button.js-move-storage-gridfs
    -              i.fa.fa-arrow-right
    +              | ➡️
                   | {{_ 'attachment-move-storage-gridfs'}}
     
             if $neq version.storageName "s3"
               if version.storageName
                 button.js-move-storage-s3
    -              i.fa.fa-arrow-right
    +              | ➡️
                   | {{_ 'attachment-move-storage-s3'}}
    diff --git a/client/components/settings/attachments.js b/client/components/settings/attachments.js
    index 3b4fc8e47..82dc78cca 100644
    --- a/client/components/settings/attachments.js
    +++ b/client/components/settings/attachments.js
    @@ -2,31 +2,31 @@ import { ReactiveCache } from '/imports/reactiveCache';
     import Attachments, { fileStoreStrategyFactory } from '/models/attachments';
     const filesize = require('filesize');
     
    -Template.attachments.onCreated(function () {
    -  this.subscription = null;
    -  this.showMoveAttachments = new ReactiveVar(false);
    -  this.sessionId = null;
    -  this.error = new ReactiveVar('');
    -  this.loading = new ReactiveVar(false);
    -});
    +BlazeComponent.extendComponent({
    +  subscription: null,
    +  showMoveAttachments: new ReactiveVar(false),
    +  sessionId: null,
     
    -Template.attachments.helpers({
    -  loading() {
    -    return Template.instance().loading;
    +  onCreated() {
    +    this.error = new ReactiveVar('');
    +    this.loading = new ReactiveVar(false);
       },
    -  showMoveAttachments() {
    -    return Template.instance().showMoveAttachments;
    -  },
    -});
     
    -Template.attachments.events({
    -  'click a.js-move-attachments'(event, tpl) {
    +  events() {
    +    return [
    +      {
    +        'click a.js-move-attachments': this.switchMenu,
    +      },
    +    ];
    +  },
    +
    +  switchMenu(event) {
         const target = $(event.target);
         if (!target.hasClass('active')) {
    -      tpl.loading.set(true);
    -      tpl.showMoveAttachments.set(false);
    -      if (tpl.subscription) {
    -        tpl.subscription.stop();
    +      this.loading.set(true);
    +      this.showMoveAttachments.set(false);
    +      if (this.subscription) {
    +        this.subscription.stop();
           }
     
           $('.side-menu li.active').removeClass('active');
    @@ -34,30 +34,25 @@ Template.attachments.events({
           const targetID = target.data('id');
     
           if ('move-attachments' === targetID) {
    -        tpl.showMoveAttachments.set(true);
    -        tpl.subscription = Meteor.subscribe('attachmentsList', () => {
    -          tpl.loading.set(false);
    +        this.showMoveAttachments.set(true);
    +        this.subscription = Meteor.subscribe('attachmentsList', () => {
    +          this.loading.set(false);
             });
           }
         }
       },
    -});
    +}).register('attachments');
     
    -Template.moveAttachments.onCreated(function () {
    -  this.attachments = null;
    -});
    -
    -Template.moveAttachments.helpers({
    +BlazeComponent.extendComponent({
       getBoardsWithAttachments() {
    -    const tpl = Template.instance();
    -    tpl.attachments = ReactiveCache.getAttachments();
    -    const attachmentsByBoardId = _.chain(tpl.attachments)
    +    this.attachments = ReactiveCache.getAttachments();
    +    this.attachmentsByBoardId = _.chain(this.attachments)
           .groupBy(fileObj => fileObj.meta.boardId)
           .value();
     
    -    const ret = Object.keys(attachmentsByBoardId)
    +    const ret = Object.keys(this.attachmentsByBoardId)
           .map(boardId => {
    -        const boardAttachments = attachmentsByBoardId[boardId];
    +        const boardAttachments = this.attachmentsByBoardId[boardId];
     
             _.each(boardAttachments, _attachment => {
               _attachment.flatVersion = Object.keys(_attachment.versions)
    @@ -77,65 +72,71 @@ Template.moveAttachments.helpers({
         const ret = ReactiveCache.getBoard(boardId);
         return ret;
       },
    -});
    +  events() {
    +    return [
    +      {
    +        'click button.js-move-all-attachments-to-fs'(event) {
    +          this.attachments.forEach(_attachment => {
    +            Meteor.call('moveAttachmentToStorage', _attachment._id, "fs");
    +          });
    +        },
    +        'click button.js-move-all-attachments-to-gridfs'(event) {
    +          this.attachments.forEach(_attachment => {
    +            Meteor.call('moveAttachmentToStorage', _attachment._id, "gridfs");
    +          });
    +        },
    +        'click button.js-move-all-attachments-to-s3'(event) {
    +          this.attachments.forEach(_attachment => {
    +            Meteor.call('moveAttachmentToStorage', _attachment._id, "s3");
    +          });
    +        },
    +      }
    +    ]
    +  }
    +}).register('moveAttachments');
     
    -Template.moveAttachments.events({
    -  'click button.js-move-all-attachments-to-fs'(event, tpl) {
    -    tpl.attachments.forEach(_attachment => {
    -      Meteor.call('moveAttachmentToStorage', _attachment._id, "fs");
    -    });
    +BlazeComponent.extendComponent({
    +  events() {
    +    return [
    +      {
    +        'click button.js-move-all-attachments-of-board-to-fs'(event) {
    +          this.data().attachments.forEach(_attachment => {
    +            Meteor.call('moveAttachmentToStorage', _attachment._id, "fs");
    +          });
    +        },
    +        'click button.js-move-all-attachments-of-board-to-gridfs'(event) {
    +          this.data().attachments.forEach(_attachment => {
    +            Meteor.call('moveAttachmentToStorage', _attachment._id, "gridfs");
    +          });
    +        },
    +        'click button.js-move-all-attachments-of-board-to-s3'(event) {
    +          this.data().attachments.forEach(_attachment => {
    +            Meteor.call('moveAttachmentToStorage', _attachment._id, "s3");
    +          });
    +        },
    +      }
    +    ]
       },
    -  'click button.js-move-all-attachments-to-gridfs'(event, tpl) {
    -    tpl.attachments.forEach(_attachment => {
    -      Meteor.call('moveAttachmentToStorage', _attachment._id, "gridfs");
    -    });
    -  },
    -  'click button.js-move-all-attachments-to-s3'(event, tpl) {
    -    tpl.attachments.forEach(_attachment => {
    -      Meteor.call('moveAttachmentToStorage', _attachment._id, "s3");
    -    });
    -  },
    -});
    +}).register('moveBoardAttachments');
     
    -Template.moveBoardAttachments.events({
    -  'click button.js-move-all-attachments-of-board-to-fs'() {
    -    const data = Template.currentData();
    -    data.attachments.forEach(_attachment => {
    -      Meteor.call('moveAttachmentToStorage', _attachment._id, "fs");
    -    });
    -  },
    -  'click button.js-move-all-attachments-of-board-to-gridfs'() {
    -    const data = Template.currentData();
    -    data.attachments.forEach(_attachment => {
    -      Meteor.call('moveAttachmentToStorage', _attachment._id, "gridfs");
    -    });
    -  },
    -  'click button.js-move-all-attachments-of-board-to-s3'() {
    -    const data = Template.currentData();
    -    data.attachments.forEach(_attachment => {
    -      Meteor.call('moveAttachmentToStorage', _attachment._id, "s3");
    -    });
    -  },
    -});
    -
    -Template.moveAttachment.helpers({
    +BlazeComponent.extendComponent({
       fileSize(size) {
         const ret = filesize(size);
         return ret;
       },
    -});
    -
    -Template.moveAttachment.events({
    -  'click button.js-move-storage-fs'() {
    -    const data = Template.currentData();
    -    Meteor.call('moveAttachmentToStorage', data._id, "fs");
    +  events() {
    +    return [
    +      {
    +        'click button.js-move-storage-fs'(event) {
    +          Meteor.call('moveAttachmentToStorage', this.data()._id, "fs");
    +        },
    +        'click button.js-move-storage-gridfs'(event) {
    +          Meteor.call('moveAttachmentToStorage', this.data()._id, "gridfs");
    +        },
    +        'click button.js-move-storage-s3'(event) {
    +          Meteor.call('moveAttachmentToStorage', this.data()._id, "s3");
    +        },
    +      }
    +    ]
       },
    -  'click button.js-move-storage-gridfs'() {
    -    const data = Template.currentData();
    -    Meteor.call('moveAttachmentToStorage', data._id, "gridfs");
    -  },
    -  'click button.js-move-storage-s3'() {
    -    const data = Template.currentData();
    -    Meteor.call('moveAttachmentToStorage', data._id, "s3");
    -  },
    -});
    +}).register('moveAttachment');
    diff --git a/client/components/settings/cronSettings.css b/client/components/settings/cronSettings.css
    index f3f8293ed..95c0d70ff 100644
    --- a/client/components/settings/cronSettings.css
    +++ b/client/components/settings/cronSettings.css
    @@ -838,67 +838,27 @@
         align-items: flex-start;
         gap: 15px;
       }
    -
    +  
       .migration-controls,
       .jobs-controls {
         width: 100%;
         justify-content: center;
       }
    -
    +  
       .table {
         font-size: 12px;
       }
    -
    +  
       .table th,
       .table td {
         padding: 8px 12px;
       }
    -
    +  
       .btn-group {
         flex-direction: column;
       }
    -
    +  
       .add-job-form {
         max-width: 100%;
       }
     }
    -
    -/* Progress bar styles for #cron-setting section */
    -#cron-setting .progress-section {
    -  margin-top: 15px;
    -}
    -
    -#cron-setting .step-counter {
    -  margin-bottom: 8px;
    -  font-weight: 600;
    -  color: #333;
    -  font-size: 14px;
    -}
    -
    -#cron-setting .progress {
    -  height: 30px;
    -  background-color: #e9ecef;
    -  border-radius: 4px;
    -  overflow: visible;
    -  margin-bottom: 5px;
    -  max-width: calc(100% - 40px);
    -}
    -
    -#cron-setting .progress-bar {
    -  height: 30px;
    -  line-height: 30px;
    -  background-color: #28a745;
    -  color: white;
    -  font-weight: 600;
    -  font-size: 14px;
    -  text-align: center;
    -  transition: width 0.3s ease;
    -  border-radius: 4px;
    -}
    -
    -#cron-setting .progress-text {
    -  font-size: 13px;
    -  color: #666;
    -  margin-top: 5px;
    -  max-width: calc(100% - 40px);
    -}
    diff --git a/client/components/settings/cronSettings.jade b/client/components/settings/cronSettings.jade
    index 906f1adaf..b1b71a0d1 100644
    --- a/client/components/settings/cronSettings.jade
    +++ b/client/components/settings/cronSettings.jade
    @@ -2,54 +2,23 @@ template(name="cronSettings")
       ul#cron-setting.setting-detail
         li
           h3 {{_ 'cron-migrations'}}
    -      .form-group
    -        label {{_ 'select-migration'}}
    -        select.js-migration-select.wekan-form-control
    -          option(value="0") 0 - {{_ 'all-migrations'}}
    -          each migrationStepsWithIndex
    -            option(value="{{index}}") {{index}} - {{name}}
    -
           .form-group
             label {{_ 'migration-status'}}
             .status-indicator
    +          span.status-label {{_ 'status'}}:
               span.status-value {{migrationStatus}}
    -        if isMigrating
    -          .progress-section
    -            .step-counter
    -              | Step {{migrationCurrentStepNum}}/{{migrationTotalSteps}}
    -            .progress
    -              .progress-bar(role="progressbar" style="width: {{migrationProgress}}%" aria-valuenow="{{migrationProgress}}" aria-valuemin="0" aria-valuemax="100")
    -                | {{migrationProgress}}%
    -            .progress-text
    -              | {{migrationProgress}}% {{_ 'complete'}}
    -
    +        .progress-section
    +          .progress
    +            .progress-bar(role="progressbar" style="width: {{migrationProgress}}%" aria-valuenow="{{migrationProgress}}" aria-valuemin="0" aria-valuemax="100") 
    +              | {{migrationProgress}}%
    +          .progress-text
    +            | {{migrationProgress}}% {{_ 'complete'}}
    +      
           .form-group
    -        button.js-start-migration.btn.btn-primary(disabled="{{#if isMigrating}}disabled{{/if}}") {{_ 'start'}}
    -        button.js-pause-migration.btn.btn-warning(disabled="{{#unless isMigrating}}disabled{{/unless}}") {{_ 'pause'}}
    -        button.js-stop-migration.btn.btn-danger(disabled="{{#unless isMigrating}}disabled{{/unless}}") {{_ 'stop'}}
    -
    -      .form-group.migration-errors-section
    -        h4 {{_ 'cron-migration-errors'}}
    -        if hasErrors
    -          .error-actions
    -            button.js-clear-all-errors.btn.btn-sm.btn-warning {{_ 'cron-clear-errors'}}
    -          .errors-list
    -            each migrationErrors
    -              .error-item(class="error-{{severity}}")
    -                .error-header
    -                  span.error-severity(class="severity-{{severity}}") {{severity}}
    -                  span.error-time {{formatDateTime createdAt}}
    -                  if stepId
    -                    span.error-step {{stepId}}
    -                .error-message {{errorMessage}}
    -                if context
    -                  .error-context
    -                    each contextValue context
    -                      span.context-item {{this}}
    -        else
    -          .no-errors
    -            | {{_ 'cron-no-errors'}}
    -
    +        button.js-start-all-migrations.btn.btn-primary {{_ 'start-all-migrations'}}
    +        button.js-pause-all-migrations.btn.btn-warning {{_ 'pause-all-migrations'}}
    +        button.js-stop-all-migrations.btn.btn-danger {{_ 'stop-all-migrations'}}
    +    
         li
           h3 {{_ 'board-operations'}}
           .form-group
    @@ -57,7 +26,7 @@ template(name="cronSettings")
             button.js-schedule-board-cleanup.btn.btn-primary {{_ 'schedule-board-cleanup'}}
             button.js-schedule-board-archive.btn.btn-warning {{_ 'schedule-board-archive'}}
             button.js-schedule-board-backup.btn.btn-info {{_ 'schedule-board-backup'}}
    -
    +    
         li
           h3 {{_ 'cron-jobs'}}
           .form-group
    @@ -82,30 +51,30 @@ template(name="cronMigrations")
             | {{_ 'database-migrations'}}
           .migration-controls
             button.btn.btn-primary.js-start-all-migrations
    -          i.fa.fa-play
    +          | ▶️
               | {{_ 'start-all-migrations'}}
             button.btn.btn-warning.js-pause-all-migrations
    -          i.fa.fa-pause
    +          | ⏸️
               | {{_ 'pause-all-migrations'}}
             button.btn.btn-danger.js-stop-all-migrations
    -          i.fa.fa-stop
    +          | ⏹️
               | {{_ 'stop-all-migrations'}}
    -
    +    
         .migration-progress
           .progress-overview
             .progress-bar
    -          .progress-fill(style="width: {{migrationProgress}}%")
    +          .progress-fill(style="width: {{migrationProgress}}%") 
             .progress-text {{migrationProgress}}%
             .progress-label {{_ 'overall-progress'}}
    -
    +      
           .current-step
    -        i.fa.fa-cog
    +        | ⚙️
             | {{migrationCurrentStep}}
    -
    +      
           .migration-status
    -        i.fa.fa-info-circle
    +        | ℹ️
             | {{migrationStatus}}
    -
    +    
         .migration-steps
           h3 {{_ 'migration-steps'}}
           .steps-list
    @@ -114,11 +83,11 @@ template(name="cronMigrations")
                 .step-header
                   .step-icon
                     if completed
    -                  i.fa.fa-check
    +                  | ✅
                     else if isCurrentStep
    -                  i.fa.fa-cog
    +                  | ⚙️
                     else
    -                  i.fa.fa-circle-o
    +                  | ⭕
                   .step-info
                     .step-name {{name}}
                     .step-description {{description}}
    @@ -137,19 +106,19 @@ template(name="cronBoardOperations")
       .cron-board-operations
         .board-operations-header
           h2
    -        i.fa.fa-list
    +        | 📋
             | {{_ 'board-operations'}}
           .board-operations-controls
             button.btn.btn-success.js-refresh-board-operations
    -          i.fa.fa-recycle
    +          | 🔄
               | {{_ 'refresh'}}
             button.btn.btn-primary.js-start-test-operation
    -          i.fa.fa-play
    +          | ▶️
               | {{_ 'start-test-operation'}}
             button.btn.btn-info.js-force-board-scan
    -          i.fa.fa-search
    +          | 🔍
               | {{_ 'force-board-scan'}}
    -
    +    
         .board-operations-stats
           .stats-grid
             .stat-item
    @@ -176,7 +145,7 @@ template(name="cronBoardOperations")
             .stat-item
               .stat-value {{boardMigrationStats.isScanning}}
               .stat-label {{_ 'scanning-status'}}
    -
    +    
         .system-resources
           .resource-item
             .resource-label {{_ 'cpu-usage'}}
    @@ -191,18 +160,18 @@ template(name="cronBoardOperations")
           .resource-item
             .resource-label {{_ 'cpu-cores'}}
             .resource-value {{systemResources.cpuCores}}
    -
    +    
         .board-operations-search
           .search-box
             input.form-control.js-search-board-operations(type="text" placeholder="{{_ 'search-boards-or-operations'}}")
    -        i.fa.fa-search.search-icon
    -
    +        | 🔍.search-icon
    +    
         .board-operations-list
           .operations-header
             h3 {{_ 'board-operations'}} ({{pagination.total}})
             .pagination-info
               | {{_ 'showing'}} {{pagination.start}} - {{pagination.end}} {{_ 'of'}} {{pagination.total}}
    -
    +      
           .operations-table
             table.table.table-striped
               thead
    @@ -234,38 +203,38 @@ template(name="cronBoardOperations")
                       .btn-group
                         if isRunning
                           button.btn.btn-sm.btn-warning.js-pause-operation(data-operation="{{id}}")
    -                        i.fa.fa-pause
    +                        | ⏸️
                         else
                           button.btn.btn-sm.btn-success.js-resume-operation(data-operation="{{id}}")
    -                        i.fa.fa-play
    +                        | ▶️
                         button.btn.btn-sm.btn-danger.js-stop-operation(data-operation="{{id}}")
    -                      i.fa.fa-stop
    +                      | ⏹️
                         button.btn.btn-sm.btn-info.js-view-details(data-operation="{{id}}")
    -                      i.fa.fa-info-circle
    -
    +                      | ℹ️
    +      
           .pagination
             if pagination.hasPrev
               button.btn.btn-sm.btn-default.js-prev-page
    -            i.fa.fa-caret-left
    +            | ◀️
                 | {{_ 'previous'}}
             .page-info
               | {{_ 'page'}} {{pagination.page}} {{_ 'of'}} {{pagination.totalPages}}
             if pagination.hasNext
               button.btn.btn-sm.btn-default.js-next-page
                 | {{_ 'next'}}
    -            i.fa.fa-caret-right
    +            | ▶️
     
     template(name="cronJobs")
       .cron-jobs
         .jobs-header
           h2
    -        i.fa.fa-clock-o
    +        | ⏰
             | {{_ 'cron-jobs'}}
           .jobs-controls
             button.btn.btn-success.js-refresh-jobs
    -          i.fa.fa-refresh
    +          | 🔄
               | {{_ 'refresh'}}
    -
    +    
         .jobs-list
           table.table.table-striped
             thead
    @@ -289,32 +258,32 @@ template(name="cronJobs")
                     .btn-group
                       if isRunning
                         button.btn.btn-sm.btn-warning.js-pause-job(data-job="{{name}}")
    -                      i.fa.fa-pause
    +                      | ⏸️
                       else
                         button.btn.btn-sm.btn-success.js-start-job(data-job="{{name}}")
    -                      i.fa.fa-caret-right
    +                      | ▶️
                       button.btn.btn-sm.btn-danger.js-stop-job(data-job="{{name}}")
    -                    i.fa.fa-stop
    +                    | ⏹️
                       button.btn.btn-sm.btn-danger.js-remove-job(data-job="{{name}}")
    -                    i.fa.fa-trash
    +                    | 🗑️
     
     template(name="cronAddJob")
       .cron-add-job
         .add-job-header
           h2
    -        i.fa.fa-plus
    +        | ➕
             | {{_ 'add-cron-job'}}
    -
    +    
         .add-job-form
           form.js-add-cron-job-form
             .form-group
               label(for="job-name") {{_ 'job-name'}}
               input.form-control#job-name(type="text" name="name" required)
    -
    +        
             .form-group
               label(for="job-description") {{_ 'job-description'}}
               textarea.form-control#job-description(name="description" rows="3")
    -
    +        
             .form-group
               label(for="job-schedule") {{_ 'schedule'}}
               select.form-control#job-schedule(name="schedule")
    @@ -326,15 +295,15 @@ template(name="cronAddJob")
                 option(value="every 6 hours") {{_ 'every-6-hours'}}
                 option(value="every 1 day") {{_ 'every-1-day'}}
                 option(value="once") {{_ 'run-once'}}
    -
    +        
             .form-group
               label(for="job-weight") {{_ 'weight'}}
               input.form-control#job-weight(type="number" name="weight" value="1" min="1" max="10")
    -
    +        
             .form-actions
               button.btn.btn-primary(type="submit")
    -            i.fa.fa-plus
    +            | ➕
                 | {{_ 'add-job'}}
               button.btn.btn-default.js-cancel-add-job
    -            i.fa.fa-times-thin
    +            | ❌
                 | {{_ 'cancel'}}
    diff --git a/client/components/settings/informationBody.jade b/client/components/settings/informationBody.jade
    index 6b0265a29..6907ac9d0 100644
    --- a/client/components/settings/informationBody.jade
    +++ b/client/components/settings/informationBody.jade
    @@ -5,14 +5,14 @@ template(name='information')
         else
           .content-title
             span
    -          i.fa.fa-info-circle
    +          | ℹ️
               | {{_ 'info'}}
           .content-body
             .side-menu
               ul
                 li.active
                   a.js-setting-menu(data-id="information-display")
    -                i.fa.fa-info-circle
    +                | ℹ️
                     | {{_ 'info'}}
             .main-body
               +statistics
    diff --git a/client/components/settings/informationBody.js b/client/components/settings/informationBody.js
    index f8b7da458..294547712 100644
    --- a/client/components/settings/informationBody.js
    +++ b/client/components/settings/informationBody.js
    @@ -1,18 +1,18 @@
     import { TAPi18n } from '/imports/i18n';
     const filesize = require('filesize');
     
    -Template.statistics.onCreated(function () {
    -  this.info = new ReactiveVar({});
    -  Meteor.call('getStatistics', (error, ret) => {
    -    if (!error && ret) {
    -      this.info.set(ret);
    -    }
    -  });
    -});
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.info = new ReactiveVar({});
    +    Meteor.call('getStatistics', (error, ret) => {
    +      if (!error && ret) {
    +        this.info.set(ret);
    +      }
    +    });
    +  },
     
    -Template.statistics.helpers({
       statistics() {
    -    return Template.instance().info.get();
    +    return this.info.get();
       },
     
       humanReadableTime(time) {
    @@ -47,4 +47,4 @@ Template.statistics.helpers({
         }
         return ret;
       },
    -});
    +}).register('statistics');
    diff --git a/client/components/settings/lockedUsersBody.js b/client/components/settings/lockedUsersBody.js
    index 6fd8cb5c6..8b44609cc 100644
    --- a/client/components/settings/lockedUsersBody.js
    +++ b/client/components/settings/lockedUsersBody.js
    @@ -1,12 +1,16 @@
     import { ReactiveCache } from '/imports/reactiveCache';
     import LockoutSettings from '/models/lockoutSettings';
     
    -Template.lockedUsersGeneral.onCreated(function () {
    -  this.lockedUsers = new ReactiveVar([]);
    -  this.isLoadingLockedUsers = new ReactiveVar(false);
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.lockedUsers = new ReactiveVar([]);
    +    this.isLoadingLockedUsers = new ReactiveVar(false);
     
    -  // Store refreshLockedUsers on the instance so it can be called from event handlers
    -  this.refreshLockedUsers = () => {
    +    // Don't load immediately to prevent unnecessary spinner
    +    // The data will be loaded when the tab is selected in peopleBody.js switchMenu
    +  },
    +
    +  refreshLockedUsers() {
         // Set loading state initially, but we'll hide it if no users are found
         this.isLoadingLockedUsers.set(true);
     
    @@ -40,54 +44,9 @@ Template.lockedUsersGeneral.onCreated(function () {
           this.lockedUsers.set(users);
           this.isLoadingLockedUsers.set(false);
         });
    -  };
    -
    -  // Don't load immediately to prevent unnecessary spinner
    -  // The data will be loaded when the tab is selected in peopleBody.js switchMenu
    -});
    -
    -Template.lockedUsersGeneral.helpers({
    -  knownFailuresBeforeLockout() {
    -    return LockoutSettings.findOne('known-failuresBeforeLockout')?.value || 3;
       },
     
    -  knownLockoutPeriod() {
    -    return LockoutSettings.findOne('known-lockoutPeriod')?.value || 60;
    -  },
    -
    -  knownFailureWindow() {
    -    return LockoutSettings.findOne('known-failureWindow')?.value || 15;
    -  },
    -
    -  unknownFailuresBeforeLockout() {
    -    return LockoutSettings.findOne('unknown-failuresBeforeLockout')?.value || 3;
    -  },
    -
    -  unknownLockoutPeriod() {
    -    return LockoutSettings.findOne('unknown-lockoutPeriod')?.value || 60;
    -  },
    -
    -  unknownFailureWindow() {
    -    return LockoutSettings.findOne('unknown-failureWindow')?.value || 15;
    -  },
    -
    -  lockedUsers() {
    -    return Template.instance().lockedUsers.get();
    -  },
    -
    -  isLoadingLockedUsers() {
    -    return Template.instance().isLoadingLockedUsers.get();
    -  },
    -});
    -
    -Template.lockedUsersGeneral.events({
    -  'click button.js-refresh-locked-users'(event, tpl) {
    -    tpl.refreshLockedUsers();
    -  },
    -  'click button#refreshLockedUsers'(event, tpl) {
    -    tpl.refreshLockedUsers();
    -  },
    -  'click button.js-unlock-user'(event, tpl) {
    +  unlockUser(event) {
         const userId = $(event.currentTarget).data('user-id');
         if (!userId) return;
     
    @@ -102,12 +61,13 @@ Template.lockedUsersGeneral.events({
     
             if (result) {
               alert(TAPi18n.__('accounts-lockout-user-unlocked'));
    -          tpl.refreshLockedUsers();
    +          this.refreshLockedUsers();
             }
           });
         }
       },
    -  'click button.js-unlock-all-users'(event, tpl) {
    +
    +  unlockAllUsers() {
         if (confirm(TAPi18n.__('accounts-lockout-confirm-unlock-all'))) {
           Meteor.call('unlockAllUsers', (err, result) => {
             if (err) {
    @@ -119,12 +79,13 @@ Template.lockedUsersGeneral.events({
     
             if (result) {
               alert(TAPi18n.__('accounts-lockout-user-unlocked'));
    -          tpl.refreshLockedUsers();
    +          this.refreshLockedUsers();
             }
           });
         }
       },
    -  'click button.js-lockout-save'() {
    +
    +  saveLockoutSettings() {
         // Get values from form
         const knownFailuresBeforeLockout = parseInt($('#known-failures-before-lockout').val(), 10) || 3;
         const knownLockoutPeriod = parseInt($('#known-lockout-period').val(), 10) || 60;
    @@ -167,4 +128,48 @@ Template.lockedUsersGeneral.events({
           }
         });
       },
    -});
    +
    +  knownFailuresBeforeLockout() {
    +    return LockoutSettings.findOne('known-failuresBeforeLockout')?.value || 3;
    +  },
    +
    +  knownLockoutPeriod() {
    +    return LockoutSettings.findOne('known-lockoutPeriod')?.value || 60;
    +  },
    +
    +  knownFailureWindow() {
    +    return LockoutSettings.findOne('known-failureWindow')?.value || 15;
    +  },
    +
    +  unknownFailuresBeforeLockout() {
    +    return LockoutSettings.findOne('unknown-failuresBeforeLockout')?.value || 3;
    +  },
    +
    +  unknownLockoutPeriod() {
    +    return LockoutSettings.findOne('unknown-lockoutPeriod')?.value || 60;
    +  },
    +
    +  unknownFailureWindow() {
    +    return LockoutSettings.findOne('unknown-failureWindow')?.value || 15;
    +  },
    +
    +  lockedUsers() {
    +    return this.lockedUsers.get();
    +  },
    +
    +  isLoadingLockedUsers() {
    +    return this.isLoadingLockedUsers.get();
    +  },
    +
    +  events() {
    +    return [
    +      {
    +        'click button.js-refresh-locked-users': this.refreshLockedUsers,
    +        'click button#refreshLockedUsers': this.refreshLockedUsers,
    +        'click button.js-unlock-user': this.unlockUser,
    +        'click button.js-unlock-all-users': this.unlockAllUsers,
    +        'click button.js-lockout-save': this.saveLockoutSettings,
    +      },
    +    ];
    +  },
    +}).register('lockedUsersGeneral');
    diff --git a/client/components/settings/peopleBody.jade b/client/components/settings/peopleBody.jade
    index a44f07c55..0aa9a4dba 100644
    --- a/client/components/settings/peopleBody.jade
    +++ b/client/components/settings/peopleBody.jade
    @@ -9,34 +9,34 @@ template(name="people")
                 +spinner
               else if orgSetting.get
                 span
    -              i.fa.fa-globe
    +              | 🌐
                   unless isMiniScreen
                     | {{_ 'organizations'}}
                 input#searchOrgInput(placeholder="{{_ 'search'}}")
                 button#searchOrgButton
    -              i.fa.fa-search
    +              | 🔍
                   | {{_ 'search'}}
                 .ext-box-right
                   span {{#unless isMiniScreen}}{{_ 'org-number'}}{{/unless}} #{orgNumber}
               else if teamSetting.get
                 span
    -              i.fa.fa-users
    +              | 👥
                   unless isMiniScreen
                     | {{_ 'teams'}}
                 input#searchTeamInput(placeholder="{{_ 'search'}}")
                 button#searchTeamButton
    -              i.fa.fa-search
    +              | 🔍
                   | {{_ 'search'}}
                 .ext-box-right
                   span {{#unless isMiniScreen}}{{_ 'team-number'}}{{/unless}} #{teamNumber}
               else if peopleSetting.get
                 span
    -              i.fa.fa-user
    +              | 👤
                   unless isMiniScreen
                     | {{_ 'people'}}
                 input#searchInput(placeholder="{{_ 'search'}}")
                 button#searchButton
    -              i.fa.fa-search
    +              | 🔍
                   | {{_ 'search'}}
                 .divLockedUsersFilter
                   .flex-container
    @@ -48,19 +48,17 @@ template(name="people")
                       option(value="inactive") {{_ 'admin-people-filter-inactive'}}
                       option(value="admin") Admin
                   button#unlockAllUsers.unlock-all-btn
    -                span.emoji-icon
    -                i.fa.fa-unlock
    +                | 🔓
                     | {{_ 'accounts-lockout-unlock-all'}}
                 .ext-box-right
                   span {{#unless isMiniScreen}}{{_ 'people-number'}}{{/unless}} #{peopleNumber}
                 .divAddOrRemoveTeam#divAddOrRemoveTeam
                   button#addOrRemoveTeam
    -                i.fa.fa-pencil-square-o
    +                | ✏️
                     | {{_ 'add'}} / {{_ 'delete'}} {{_ 'teams'}}
               else if lockedUsersSetting.get
                 span
    -              span.emoji-icon.text-red
    -                i.fa.fa-lock
    +              span.text-red 🔒
                   unless isMiniScreen
                     | {{_ 'accounts-lockout-locked-users'}}
     
    @@ -69,20 +67,19 @@ template(name="people")
               ul
                 li.active
                   a.js-org-menu(data-id="org-setting")
    -                i.fa.fa-globe
    +                | 🌐
                     | {{_ 'organizations'}}
                 li
                   a.js-team-menu(data-id="team-setting")
    -                i.fa.fa-users
    +                | 👥
                     | {{_ 'teams'}}
                 li
                   a.js-people-menu(data-id="people-setting")
    -                i.fa.fa-user
    +                | 👤
                     | {{_ 'people'}}
                 li
                   a.js-locked-users-menu(data-id="locked-users-setting")
    -                span.emoji-icon.text-red
    -                  i.fa.fa-lock
    +                span.text-red 🔒
                     | {{_ 'accounts-lockout-locked-users'}}
             .main-body
               if loading.get
    @@ -138,15 +135,16 @@ template(name="peopleGeneral")
         thead
           tr
             th
    -          +newUserRow
    -        th {{_ 'username'}}
    -        th {{_ 'email'}}
    -        th {{_ 'admin'}}
    -        th {{_ 'admin-people-active-status'}}
    +          +selectAllUser
             th {{_ 'accounts-lockout-status'}}
    +        th {{_ 'admin-people-active-status'}}
    +        th {{_ 'username'}}
    +        th {{_ 'fullname'}}
    +        th {{_ 'admin'}}
    +        th {{_ 'email'}}
             th {{_ 'createdAt'}}
             th
    -          +selectAllUser
    +          +newUserRow
         tbody
           tr
           each user in peopleList
    @@ -158,17 +156,17 @@ template(name="selectAllUser")
     
     template(name="newOrgRow")
       a.new-org
    -    i.fa.fa-plus
    +    | ➕
         | {{_ 'new'}}
     
     template(name="newTeamRow")
       a.new-team
    -    i.fa.fa-plus
    +    | ➕
         | {{_ 'new'}}
     
     template(name="newUserRow")
       a.new-user
    -    i.fa.fa-plus
    +    | ➕
         | {{_ 'new'}}
     
     template(name="orgRow")
    @@ -190,9 +188,9 @@ template(name="orgRow")
         else
           td {{ orgData.orgWebsite }}
         if orgData.orgIsActive
    -      td {{ displayDate orgData.createdAt 'LLL' }}
    +      td {{ moment orgData.createdAt 'LLL' }}
         else
    -      td {{ displayDate orgData.createdAt 'LLL' }}
    +      td {{ moment orgData.createdAt 'LLL' }}
         td
           if orgData.orgIsActive
             | {{_ 'yes'}}
    @@ -200,7 +198,7 @@ template(name="orgRow")
             | {{_ 'no'}}
         td
           a.edit-org
    -        i.fa.fa-pencil-square-o
    +        | ✏️
             | {{_ 'edit'}}
           a.more-settings-org
             | ⋯
    @@ -224,9 +222,9 @@ template(name="teamRow")
         else
           td {{ teamData.teamWebsite }}
         if teamData.teamIsActive
    -      td {{ displayDate teamData.createdAt 'LLL' }}
    +      td {{ moment teamData.createdAt 'LLL' }}
         else
    -      td {{ displayDate teamData.createdAt 'LLL' }}
    +      td {{ moment teamData.createdAt 'LLL' }}
         td
           if teamData.teamIsActive
             | {{_ 'yes'}}
    @@ -234,19 +232,29 @@ template(name="teamRow")
             | {{_ 'no'}}
         td
           a.edit-team
    -        i.fa.fa-pencil-square-o
    +        | ✏️
             | {{_ 'edit'}}
           a.more-settings-team
             | ⋯
     
     template(name="peopleRow")
       tr
    -    td
    -      a.edit-user
    -        i.fa.fa-pencil-square-o
    -        | {{_ 'edit'}}
    -      a.more-settings-user
    -        | ⋯
    +    if userData.loginDisabled
    +      td
    +        input.selectUserChkBox(type="checkbox", disabled="disabled", id="{{userData._id}}")
    +    else
    +      td
    +        input.selectUserChkBox(type="checkbox", id="{{userData._id}}")
    +    td.account-status
    +      if isUserLocked
    +        span.text-red.js-toggle-lock-status(data-user-id=userData._id, data-is-locked="true", title="{{_ 'accounts-lockout-click-to-unlock'}}") 🔒
    +      else
    +        span.text-green.js-toggle-lock-status(data-user-id=userData._id, data-is-locked="false", title="{{_ 'accounts-lockout-user-unlocked'}}") 🔓
    +    td.account-active-status
    +      if userData.loginDisabled
    +        span.text-red.js-toggle-active-status(data-user-id=userData._id, data-is-active="false", title="{{_ 'admin-people-user-inactive'}}") 🚫
    +      else
    +        span.text-green.js-toggle-active-status(data-user-id=userData._id, data-is-active="true", title="{{_ 'admin-people-user-active'}}") ✅
         if userData.loginDisabled
           td.username {{ userData.username }}
         else if isUserLocked
    @@ -254,9 +262,9 @@ template(name="peopleRow")
         else
           td.username {{ userData.username }}
         if userData.loginDisabled
    -      td {{ userData.emails.[0].address }}
    +      td {{ userData.profile.fullname }}
         else
    -      td {{ userData.emails.[0].address }}
    +      td {{ userData.profile.fullname }}
         if userData.loginDisabled
           td
             if userData.isAdmin
    @@ -269,30 +277,20 @@ template(name="peopleRow")
               | {{_ 'yes'}}
             else
               | {{_ 'no'}}
    -    td.account-active-status
    -      if userData.loginDisabled
    -        span.text-red.js-toggle-active-status(data-user-id=userData._id, data-is-active="false", title="{{_ 'admin-people-user-inactive'}}")
    -          i.fa.fa-ban
    -      else
    -        span.text-green.js-toggle-active-status(data-user-id=userData._id, data-is-active="true", title="{{_ 'admin-people-user-active'}}")
    -          i.fa.fa-check
    -    td.account-status
    -      if isUserLocked
    -        span.text-red.js-toggle-lock-status.emoji-icon(data-user-id=userData._id, data-is-locked="true", title="{{_ 'accounts-lockout-click-to-unlock'}}")
    -          i.fa.fa-lock
    -      else
    -        span.text-green.js-toggle-lock-status.emoji-icon(data-user-id=userData._id, data-is-locked="false", title="{{_ 'accounts-lockout-user-unlocked'}}")
    -          i.fa.fa-unlock
         if userData.loginDisabled
    -      td {{ displayDate userData.createdAt 'LLL' }}
    +      td {{ userData.emails.[0].address }}
         else
    -      td {{ displayDate userData.createdAt 'LLL' }}
    +      td {{ userData.emails.[0].address }}
         if userData.loginDisabled
    -      td
    -        input.selectUserChkBox(type="checkbox", disabled="disabled", id="{{userData._id}}")
    +      td {{ moment userData.createdAt 'LLL' }}
         else
    -      td
    -        input.selectUserChkBox(type="checkbox", id="{{userData._id}}")
    +      td {{ moment userData.createdAt 'LLL' }}
    +    td
    +      a.edit-user
    +        | ✏️
    +        | {{_ 'edit'}}
    +      a.more-settings-user
    +        | ⋯
     
     template(name="editOrgPopup")
       form
    @@ -402,10 +400,8 @@ template(name="editUserPopup")
                 option(value="{{value}}") {{_ value}}
         label
           | {{_ 'organizations'}}
    -      span#addUserOrg
    -        i.fa.fa-plus
    -      span#removeUserOrg
    -        i.fa.fa-minus
    +      span#addUserOrg ➕
    +      span#removeUserOrg ➖
           select.js-orgs#jsOrgs
             option(value="-1") {{_ 'organizations'}} :
             each value in orgsDatas
    @@ -414,10 +410,8 @@ template(name="editUserPopup")
           input#jsUserOrgIdsInPut.js-userOrgIds.hide(type="hidden" value=user.orgIdsUserBelongs)
         label
           | {{_ 'teams'}}
    -      span#addUserTeam
    -        i.fa.fa-plus
    -      span#removeUserTeam
    -        i.fa.fa-minus
    +      span#addUserTeam ➕
    +      span#removeUserTeam ➖
           select.js-teams#jsTeams
             option(value="-1") {{_ 'teams'}} :
             each value in teamsDatas
    @@ -512,8 +506,7 @@ template(name="newUserPopup")
           span.error.hide.username-taken
             | {{_ 'error-username-taken'}}
           //if isLdap
    -      //
    -        input.js-profile-username(type="text" value=user.username readonly)
    +      //  input.js-profile-username(type="text" value=user.username readonly)
           //else
           input.js-profile-username(type="text" value="" required)
         label
    @@ -524,8 +517,7 @@ template(name="newUserPopup")
           span.error.hide.email-taken
             | {{_ 'error-email-taken'}}
           //if isLdap
    -      //
    -        input.js-profile-email(type="email" value="{{user.emails.[0].address}}" readonly)
    +      //  input.js-profile-email(type="email" value="{{user.emails.[0].address}}" readonly)
           //else
           input.js-profile-email(type="email" value="" required)
         label
    @@ -551,10 +543,8 @@ template(name="newUserPopup")
                 option(value="{{value}}") {{_ value}}
         label
           | {{_ 'organizations'}}
    -      span#addUserOrgNewUser
    -        i.fa.fa-plus
    -      span#removeUserOrgNewUser
    -        i.fa.fa-minus
    +      span#addUserOrgNewUser ➕
    +      span#removeUserOrgNewUser ➖
           select.js-orgsNewUser#jsOrgsNewUser
             option(value="-1") {{_ 'organizations'}} :
             each value in orgsDatas
    @@ -563,10 +553,8 @@ template(name="newUserPopup")
           input#jsUserOrgIdsInPutNewUser.js-userOrgIdsNewUser.hide(type="text" value=user.orgIdsUserBelongs)
         label
           | {{_ 'teams'}}
    -      span#addUserTeamNewUser
    -        i.fa.fa-plus
    -      span#removeUserTeamNewUser
    -        i.fa.fa-minus
    +      span#addUserTeamNewUser ➕
    +      span#removeUserTeamNewUser ➖
           select.js-teamsNewUser#jsTeamsNewUser
             option(value="-1") {{_ 'teams'}} :
             each value in teamsDatas
    @@ -598,14 +586,10 @@ template(name="settingsOrgPopup")
       // It's not yet possible to impersonate organization. Only impersonate user,
       // because that changes current user ID. What would it mean in practice
       // to impersonate organization?
    -  //
    -    li
    -  //
    -      a.impersonate-org
    -  //
    -        i.fa.fa-user
    -  //
    -        | {{_ 'impersonate-org'}}
    +  //  li
    +  //    a.impersonate-org
    +  //      | 👤
    +  //      | {{_ 'impersonate-org'}}
       //
       //
     
    @@ -627,7 +611,7 @@ template(name="settingsUserPopup")
       ul.pop-over-list
         li
           a.impersonate-user
    -        i.fa.fa-user
    +        | 👤
             | {{_ 'impersonate-user'}}
         br
         hr
    @@ -646,10 +630,8 @@ template(name="settingsUserPopup")
       // - wekan/client/components/settings/peopleBody.jade deleteButton
       // - wekan/client/components/settings/peopleBody.js deleteButton
       // - wekan/client/components/sidebar/sidebar.js Popup.afterConfirm('removeMember'
    -  //
    -     that does now remove member from board, card members and assignees correctly,
    -  //
    -     but that should be used to remove user from all boards similarly
    +  //   that does now remove member from board, card members and assignees correctly,
    +  //   but that should be used to remove user from all boards similarly
       // - wekan/models/users.js Delete is not enabled
     
     template(name="lockedUsersGeneral")
    diff --git a/client/components/settings/peopleBody.js b/client/components/settings/peopleBody.js
    index 47e9cbce7..9abaf7ff3 100644
    --- a/client/components/settings/peopleBody.js
    +++ b/client/components/settings/peopleBody.js
    @@ -1,7 +1,5 @@
     import { ReactiveCache } from '/imports/reactiveCache';
    -import { InfiniteScrolling } from '/client/lib/infiniteScrolling';
     import LockoutSettings from '/models/lockoutSettings';
    -import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
     
     const orgsPerPage = 25;
     const teamsPerPage = 25;
    @@ -9,73 +7,139 @@ const usersPerPage = 25;
     let userOrgsTeamsAction = ""; //poosible actions 'addOrg', 'addTeam', 'removeOrg' or 'removeTeam' when adding or modifying a user
     let selectedUserChkBoxUserIds = [];
     
    -Template.people.onCreated(function () {
    -  this.infiniteScrolling = new InfiniteScrolling();
    +BlazeComponent.extendComponent({
    +  mixins() {
    +    return [Mixins.InfiniteScrolling];
    +  },
    +  onCreated() {
    +    this.error = new ReactiveVar('');
    +    this.loading = new ReactiveVar(false);
    +    this.orgSetting = new ReactiveVar(true);
    +    this.teamSetting = new ReactiveVar(false);
    +    this.peopleSetting = new ReactiveVar(false);
    +    this.lockedUsersSetting = new ReactiveVar(false);
    +    this.findOrgsOptions = new ReactiveVar({});
    +    this.findTeamsOptions = new ReactiveVar({});
    +    this.findUsersOptions = new ReactiveVar({});
    +    this.numberOrgs = new ReactiveVar(0);
    +    this.numberTeams = new ReactiveVar(0);
    +    this.numberPeople = new ReactiveVar(0);
    +    this.userFilterType = new ReactiveVar('all');
     
    -  this.error = new ReactiveVar('');
    -  this.loading = new ReactiveVar(false);
    -  this.orgSetting = new ReactiveVar(true);
    -  this.teamSetting = new ReactiveVar(false);
    -  this.peopleSetting = new ReactiveVar(false);
    -  this.lockedUsersSetting = new ReactiveVar(false);
    -  this.findOrgsOptions = new ReactiveVar({});
    -  this.findTeamsOptions = new ReactiveVar({});
    -  this.findUsersOptions = new ReactiveVar({});
    -  this.numberOrgs = new ReactiveVar(0);
    -  this.numberTeams = new ReactiveVar(0);
    -  this.numberPeople = new ReactiveVar(0);
    -  this.userFilterType = new ReactiveVar('all');
    +    this.page = new ReactiveVar(1);
    +    this.loadNextPageLocked = false;
    +    this.callFirstWith(null, 'resetNextPeak');
    +    this.autorun(() => {
    +      const limitOrgs = this.page.get() * orgsPerPage;
    +      const limitTeams = this.page.get() * teamsPerPage;
    +      const limitUsers = this.page.get() * usersPerPage;
     
    -  this.page = new ReactiveVar(1);
    -  this.loadNextPageLocked = false;
    -  this.infiniteScrolling.resetNextPeak();
    -
    -  this.calculateNextPeak = () => {
    -    const element = this.find('.main-body');
    -    if (element) {
    -      const altitude = element.scrollHeight;
    -      this.infiniteScrolling.setNextPeak(altitude);
    -    }
    -  };
    -
    -  this.loadNextPage = () => {
    -    if (this.loadNextPageLocked === false) {
    -      this.page.set(this.page.get() + 1);
    -      this.loadNextPageLocked = true;
    -    }
    -  };
    -
    -  this.filterOrg = () => {
    -    const value = $('#searchOrgInput').first().val();
    -    if (value !== '') {
    -      const regex = new RegExp(value, 'i');
    -      this.findOrgsOptions.set({
    -        $or: [
    -          { orgDisplayName: regex },
    -          { orgShortName: regex },
    -        ],
    +      this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => {
    +        this.loadNextPageLocked = false;
    +        const nextPeakBefore = this.callFirstWith(null, 'getNextPeak');
    +        this.calculateNextPeak();
    +        const nextPeakAfter = this.callFirstWith(null, 'getNextPeak');
    +        if (nextPeakBefore === nextPeakAfter) {
    +          this.callFirstWith(null, 'resetNextPeak');
    +        }
           });
    -    } else {
    -      this.findOrgsOptions.set({});
    -    }
    -  };
     
    -  this.filterTeam = () => {
    -    const value = $('#searchTeamInput').first().val();
    -    if (value !== '') {
    -      const regex = new RegExp(value, 'i');
    -      this.findTeamsOptions.set({
    -        $or: [
    -          { teamDisplayName: regex },
    -          { teamShortName: regex },
    -        ],
    +      this.subscribe('team', this.findTeamsOptions.get(), limitTeams, () => {
    +        this.loadNextPageLocked = false;
    +        const nextPeakBefore = this.callFirstWith(null, 'getNextPeak');
    +        this.calculateNextPeak();
    +        const nextPeakAfter = this.callFirstWith(null, 'getNextPeak');
    +        if (nextPeakBefore === nextPeakAfter) {
    +          this.callFirstWith(null, 'resetNextPeak');
    +        }
           });
    -    } else {
    -      this.findTeamsOptions.set({});
    -    }
    -  };
     
    -  this.filterPeople = () => {
    +      this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => {
    +        this.loadNextPageLocked = false;
    +        const nextPeakBefore = this.callFirstWith(null, 'getNextPeak');
    +        this.calculateNextPeak();
    +        const nextPeakAfter = this.callFirstWith(null, 'getNextPeak');
    +        if (nextPeakBefore === nextPeakAfter) {
    +          this.callFirstWith(null, 'resetNextPeak');
    +        }
    +      });
    +    });
    +  },
    +  events() {
    +    return [
    +      {
    +        'click #searchOrgButton'() {
    +          this.filterOrg();
    +        },
    +        'keydown #searchOrgInput'(event) {
    +          if (event.keyCode === 13 && !event.shiftKey) {
    +            this.filterOrg();
    +          }
    +        },
    +        'click #searchTeamButton'() {
    +          this.filterTeam();
    +        },
    +        'keydown #searchTeamInput'(event) {
    +          if (event.keyCode === 13 && !event.shiftKey) {
    +            this.filterTeam();
    +          }
    +        },
    +        'click #searchButton'() {
    +          this.filterPeople();
    +        },
    +        'click #addOrRemoveTeam'(){
    +          document.getElementById("divAddOrRemoveTeamContainer").style.display = 'block';
    +        },
    +        'keydown #searchInput'(event) {
    +          if (event.keyCode === 13 && !event.shiftKey) {
    +            this.filterPeople();
    +          }
    +        },
    +        'change #userFilterSelect'(event) {
    +          const filterType = $(event.target).val();
    +          this.userFilterType.set(filterType);
    +          this.filterPeople();
    +        },
    +        'click #unlockAllUsers'(event) {
    +          event.preventDefault();
    +          if (confirm(TAPi18n.__('accounts-lockout-confirm-unlock-all'))) {
    +            Meteor.call('unlockAllUsers', (error) => {
    +              if (error) {
    +                console.error('Error unlocking all users:', error);
    +              } else {
    +                // Show a brief success message
    +                const message = document.createElement('div');
    +                message.className = 'unlock-all-success';
    +                message.textContent = TAPi18n.__('accounts-lockout-all-users-unlocked');
    +                document.body.appendChild(message);
    +
    +                // Remove the message after a short delay
    +                setTimeout(() => {
    +                  if (message.parentNode) {
    +                    message.parentNode.removeChild(message);
    +                  }
    +                }, 3000);
    +              }
    +            });
    +          }
    +        },
    +        'click #newOrgButton'() {
    +          Popup.open('newOrg');
    +        },
    +        'click #newTeamButton'() {
    +          Popup.open('newTeam');
    +        },
    +        'click #newUserButton'() {
    +          Popup.open('newUser');
    +        },
    +        'click a.js-org-menu': this.switchMenu,
    +        'click a.js-team-menu': this.switchMenu,
    +        'click a.js-people-menu': this.switchMenu,
    +        'click a.js-locked-users-menu': this.switchMenu,
    +      },
    +    ];
    +  },
    +  filterPeople() {
         const value = $('#searchInput').first().val();
         const filterType = this.userFilterType.get();
         const currentTime = Number(new Date());
    @@ -119,9 +183,72 @@ Template.people.onCreated(function () {
         }
     
         this.findUsersOptions.set(query);
    -  };
    -
    -  this.switchMenu = (event) => {
    +  },
    +  loadNextPage() {
    +    if (this.loadNextPageLocked === false) {
    +      this.page.set(this.page.get() + 1);
    +      this.loadNextPageLocked = true;
    +    }
    +  },
    +  calculateNextPeak() {
    +    const element = this.find('.main-body');
    +    if (element) {
    +      const altitude = element.scrollHeight;
    +      this.callFirstWith(this, 'setNextPeak', altitude);
    +    }
    +  },
    +  reachNextPeak() {
    +    this.loadNextPage();
    +  },
    +  setError(error) {
    +    this.error.set(error);
    +  },
    +  setLoading(w) {
    +    this.loading.set(w);
    +  },
    +  orgList() {
    +    const limitOrgs = this.page.get() * orgsPerPage;
    +    const orgs = ReactiveCache.getOrgs(this.findOrgsOptions.get(), {
    +      sort: { orgDisplayName: 1 },
    +      limit: limitOrgs,
    +      fields: { _id: true },
    +    });
    +    // Count only the items currently loaded to browser, not total from database
    +    this.numberOrgs.set(orgs.length);
    +    return orgs;
    +  },
    +  teamList() {
    +    const limitTeams = this.page.get() * teamsPerPage;
    +    const teams = ReactiveCache.getTeams(this.findTeamsOptions.get(), {
    +      sort: { teamDisplayName: 1 },
    +      limit: limitTeams,
    +      fields: { _id: true },
    +    });
    +    // Count only the items currently loaded to browser, not total from database
    +    this.numberTeams.set(teams.length);
    +    return teams;
    +  },
    +  peopleList() {
    +    const limitUsers = this.page.get() * usersPerPage;
    +    const users = ReactiveCache.getUsers(this.findUsersOptions.get(), {
    +      sort: { username: 1 },
    +      limit: limitUsers,
    +      fields: { _id: true },
    +    });
    +    // Count only the items currently loaded to browser, not total from database
    +    this.numberPeople.set(users.length);
    +    return users;
    +  },
    +  orgNumber() {
    +    return this.numberOrgs.get();
    +  },
    +  teamNumber() {
    +    return this.numberTeams.get();
    +  },
    +  peopleNumber() {
    +    return this.numberPeople.get();
    +  },
    +  switchMenu(event) {
         const target = $(event.target);
         if (!target.hasClass('active')) {
           $('.side-menu li.active').removeClass('active');
    @@ -141,191 +268,8 @@ Template.people.onCreated(function () {
             }
           }
         }
    -  };
    -
    -  this.autorun(() => {
    -    const limitOrgs = this.page.get() * orgsPerPage;
    -    const limitTeams = this.page.get() * teamsPerPage;
    -    const limitUsers = this.page.get() * usersPerPage;
    -
    -    this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => {
    -      this.loadNextPageLocked = false;
    -      const nextPeakBefore = this.infiniteScrolling.getNextPeak();
    -      this.calculateNextPeak();
    -      const nextPeakAfter = this.infiniteScrolling.getNextPeak();
    -      if (nextPeakBefore === nextPeakAfter) {
    -        this.infiniteScrolling.resetNextPeak();
    -      }
    -    });
    -
    -    this.subscribe('team', this.findTeamsOptions.get(), limitTeams, () => {
    -      this.loadNextPageLocked = false;
    -      const nextPeakBefore = this.infiniteScrolling.getNextPeak();
    -      this.calculateNextPeak();
    -      const nextPeakAfter = this.infiniteScrolling.getNextPeak();
    -      if (nextPeakBefore === nextPeakAfter) {
    -        this.infiniteScrolling.resetNextPeak();
    -      }
    -    });
    -
    -    this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => {
    -      this.loadNextPageLocked = false;
    -      const nextPeakBefore = this.infiniteScrolling.getNextPeak();
    -      this.calculateNextPeak();
    -      const nextPeakAfter = this.infiniteScrolling.getNextPeak();
    -      if (nextPeakBefore === nextPeakAfter) {
    -        this.infiniteScrolling.resetNextPeak();
    -      }
    -    });
    -  });
    -});
    -
    -Template.people.helpers({
    -  loading() {
    -    return Template.instance().loading;
       },
    -  orgSetting() {
    -    return Template.instance().orgSetting;
    -  },
    -  teamSetting() {
    -    return Template.instance().teamSetting;
    -  },
    -  peopleSetting() {
    -    return Template.instance().peopleSetting;
    -  },
    -  lockedUsersSetting() {
    -    return Template.instance().lockedUsersSetting;
    -  },
    -  orgList() {
    -    const tpl = Template.instance();
    -    const limitOrgs = tpl.page.get() * orgsPerPage;
    -    const orgs = ReactiveCache.getOrgs(tpl.findOrgsOptions.get(), {
    -      sort: { orgDisplayName: 1 },
    -      limit: limitOrgs,
    -      fields: { _id: true },
    -    });
    -    // Count only the items currently loaded to browser, not total from database
    -    tpl.numberOrgs.set(orgs.length);
    -    return orgs;
    -  },
    -  teamList() {
    -    const tpl = Template.instance();
    -    const limitTeams = tpl.page.get() * teamsPerPage;
    -    const teams = ReactiveCache.getTeams(tpl.findTeamsOptions.get(), {
    -      sort: { teamDisplayName: 1 },
    -      limit: limitTeams,
    -      fields: { _id: true },
    -    });
    -    // Count only the items currently loaded to browser, not total from database
    -    tpl.numberTeams.set(teams.length);
    -    return teams;
    -  },
    -  peopleList() {
    -    const tpl = Template.instance();
    -    const limitUsers = tpl.page.get() * usersPerPage;
    -    const users = ReactiveCache.getUsers(tpl.findUsersOptions.get(), {
    -      sort: { username: 1 },
    -      limit: limitUsers,
    -      fields: { _id: true },
    -    });
    -    // Count only the items currently loaded to browser, not total from database
    -    tpl.numberPeople.set(users.length);
    -    return users;
    -  },
    -  orgNumber() {
    -    return Template.instance().numberOrgs.get();
    -  },
    -  teamNumber() {
    -    return Template.instance().numberTeams.get();
    -  },
    -  peopleNumber() {
    -    return Template.instance().numberPeople.get();
    -  },
    -});
    -
    -Template.people.events({
    -  'scroll .main-body'(event, tpl) {
    -    tpl.infiniteScrolling.checkScrollPosition(event.currentTarget, () => {
    -      tpl.loadNextPage();
    -    });
    -  },
    -  'click #searchOrgButton'(event, tpl) {
    -    tpl.filterOrg();
    -  },
    -  'keydown #searchOrgInput'(event, tpl) {
    -    if (event.keyCode === 13 && !event.shiftKey) {
    -      tpl.filterOrg();
    -    }
    -  },
    -  'click #searchTeamButton'(event, tpl) {
    -    tpl.filterTeam();
    -  },
    -  'keydown #searchTeamInput'(event, tpl) {
    -    if (event.keyCode === 13 && !event.shiftKey) {
    -      tpl.filterTeam();
    -    }
    -  },
    -  'click #searchButton'(event, tpl) {
    -    tpl.filterPeople();
    -  },
    -  'click #addOrRemoveTeam'(){
    -    document.getElementById("divAddOrRemoveTeamContainer").style.display = 'block';
    -  },
    -  'keydown #searchInput'(event, tpl) {
    -    if (event.keyCode === 13 && !event.shiftKey) {
    -      tpl.filterPeople();
    -    }
    -  },
    -  'change #userFilterSelect'(event, tpl) {
    -    const filterType = $(event.target).val();
    -    tpl.userFilterType.set(filterType);
    -    tpl.filterPeople();
    -  },
    -  'click #unlockAllUsers'(event) {
    -    event.preventDefault();
    -    if (confirm(TAPi18n.__('accounts-lockout-confirm-unlock-all'))) {
    -      Meteor.call('unlockAllUsers', (error) => {
    -        if (error) {
    -          console.error('Error unlocking all users:', error);
    -        } else {
    -          // Show a brief success message
    -          const message = document.createElement('div');
    -          message.className = 'unlock-all-success';
    -          message.textContent = TAPi18n.__('accounts-lockout-all-users-unlocked');
    -          document.body.appendChild(message);
    -
    -          // Remove the message after a short delay
    -          setTimeout(() => {
    -            if (message.parentNode) {
    -              message.parentNode.removeChild(message);
    -            }
    -          }, 3000);
    -        }
    -      });
    -    }
    -  },
    -  'click #newOrgButton'() {
    -    Popup.open('newOrg');
    -  },
    -  'click #newTeamButton'() {
    -    Popup.open('newTeam');
    -  },
    -  'click #newUserButton'() {
    -    Popup.open('newUser');
    -  },
    -  'click a.js-org-menu'(event, tpl) {
    -    tpl.switchMenu(event);
    -  },
    -  'click a.js-team-menu'(event, tpl) {
    -    tpl.switchMenu(event);
    -  },
    -  'click a.js-people-menu'(event, tpl) {
    -    tpl.switchMenu(event);
    -  },
    -  'click a.js-locked-users-menu'(event, tpl) {
    -    tpl.switchMenu(event);
    -  },
    -});
    +}).register('people');
     
     Template.orgRow.helpers({
       orgData() {
    @@ -520,201 +464,259 @@ Template.newUserPopup.helpers({
       },
     });
     
    -Template.orgRow.events({
    -  'click a.edit-org': Popup.open('editOrg'),
    -  'click a.more-settings-org': Popup.open('settingsOrg'),
    -});
    -
    -Template.teamRow.events({
    -  'click a.edit-team': Popup.open('editTeam'),
    -  'click a.more-settings-team': Popup.open('settingsTeam'),
    -});
    -
    -Template.peopleRow.events({
    -  'click a.edit-user': Popup.open('editUser'),
    -  'click a.more-settings-user': Popup.open('settingsUser'),
    -  'click .selectUserChkBox': function(ev){
    -      if(ev.currentTarget){
    -        if(ev.currentTarget.checked){
    -          if(!selectedUserChkBoxUserIds.includes(ev.currentTarget.id)){
    -            selectedUserChkBoxUserIds.push(ev.currentTarget.id);
    -          }
    -        }
    -        else{
    -          if(selectedUserChkBoxUserIds.includes(ev.currentTarget.id)){
    -            let index = selectedUserChkBoxUserIds.indexOf(ev.currentTarget.id);
    -            if(index > -1)
    -              selectedUserChkBoxUserIds.splice(index, 1);
    -          }
    -        }
    -      }
    -      if(selectedUserChkBoxUserIds.length > 0)
    -        document.getElementById("divAddOrRemoveTeam").style.display = 'block';
    -      else
    -        document.getElementById("divAddOrRemoveTeam").style.display = 'none';
    +BlazeComponent.extendComponent({
    +  onCreated() {},
    +  org() {
    +    return ReactiveCache.getOrg(this.orgId);
       },
    -  'click .js-toggle-active-status': function(ev) {
    -      ev.preventDefault();
    -      const userId = this.userId;
    -      const user = ReactiveCache.getUser(userId);
    -
    -      if (!user) return;
    -
    -      // Toggle loginDisabled status
    -      const isActive = !(user.loginDisabled === true);
    -
    -      // Update the user's active status
    -      Users.update(userId, {
    -        $set: {
    -          loginDisabled: isActive
    -        }
    -      });
    +  events() {
    +    return [
    +      {
    +        'click a.edit-org': Popup.open('editOrg'),
    +        'click a.more-settings-org': Popup.open('settingsOrg'),
    +      },
    +    ];
       },
    -  'click .js-toggle-lock-status': function(ev){
    -      ev.preventDefault();
    -      const userId = this.userId;
    -      const user = ReactiveCache.getUser(userId);
    +}).register('orgRow');
     
    -      if (!user) return;
    -
    -      // Check if user is currently locked
    -      const isLocked = user.services &&
    -          user.services['accounts-lockout'] &&
    -          user.services['accounts-lockout'].unlockTime &&
    -          user.services['accounts-lockout'].unlockTime > Number(new Date());
    -
    -      if (isLocked) {
    -        // Unlock the user
    -        Meteor.call('unlockUser', userId, (error) => {
    -          if (error) {
    -            console.error('Error unlocking user:', error);
    -          }
    -        });
    -      } else {
    -        // Lock the user - this is optional, you may want to only allow unlocking
    -        // If you want to implement locking too, you would need a server method for it
    -        // For now, we'll leave this as a no-op
    -      }
    +BlazeComponent.extendComponent({
    +  onCreated() {},
    +  team() {
    +    return ReactiveCache.getTeam(this.teamId);
       },
    -});
    +  events() {
    +    return [
    +      {
    +        'click a.edit-team': Popup.open('editTeam'),
    +        'click a.more-settings-team': Popup.open('settingsTeam'),
    +      },
    +    ];
    +  },
    +}).register('teamRow');
     
    -Template.modifyTeamsUsers.helpers({
    +BlazeComponent.extendComponent({
    +  onCreated() {},
    +  user() {
    +    return ReactiveCache.getUser(this.userId);
    +  },
    +  events() {
    +    return [
    +      {
    +        'click a.edit-user': Popup.open('editUser'),
    +        'click a.more-settings-user': Popup.open('settingsUser'),
    +        'click .selectUserChkBox': function(ev){
    +            if(ev.currentTarget){
    +              if(ev.currentTarget.checked){
    +                if(!selectedUserChkBoxUserIds.includes(ev.currentTarget.id)){
    +                  selectedUserChkBoxUserIds.push(ev.currentTarget.id);
    +                }
    +              }
    +              else{
    +                if(selectedUserChkBoxUserIds.includes(ev.currentTarget.id)){
    +                  let index = selectedUserChkBoxUserIds.indexOf(ev.currentTarget.id);
    +                  if(index > -1)
    +                    selectedUserChkBoxUserIds.splice(index, 1);
    +                }
    +              }
    +            }
    +            if(selectedUserChkBoxUserIds.length > 0)
    +              document.getElementById("divAddOrRemoveTeam").style.display = 'block';
    +            else
    +              document.getElementById("divAddOrRemoveTeam").style.display = 'none';
    +        },
    +        'click .js-toggle-active-status': function(ev) {
    +            ev.preventDefault();
    +            const userId = this.userId;
    +            const user = ReactiveCache.getUser(userId);
    +
    +            if (!user) return;
    +
    +            // Toggle loginDisabled status
    +            const isActive = !(user.loginDisabled === true);
    +
    +            // Update the user's active status
    +            Users.update(userId, {
    +              $set: {
    +                loginDisabled: isActive
    +              }
    +            });
    +        },
    +        'click .js-toggle-lock-status': function(ev){
    +            ev.preventDefault();
    +            const userId = this.userId;
    +            const user = ReactiveCache.getUser(userId);
    +
    +            if (!user) return;
    +
    +            // Check if user is currently locked
    +            const isLocked = user.services &&
    +                user.services['accounts-lockout'] &&
    +                user.services['accounts-lockout'].unlockTime &&
    +                user.services['accounts-lockout'].unlockTime > Number(new Date());
    +
    +            if (isLocked) {
    +              // Unlock the user
    +              Meteor.call('unlockUser', userId, (error) => {
    +                if (error) {
    +                  console.error('Error unlocking user:', error);
    +                }
    +              });
    +            } else {
    +              // Lock the user - this is optional, you may want to only allow unlocking
    +              // If you want to implement locking too, you would need a server method for it
    +              // For now, we'll leave this as a no-op
    +            }
    +        },
    +      },
    +    ];
    +  },
    +}).register('peopleRow');
    +
    +BlazeComponent.extendComponent({
    +  onCreated() {},
       teamsDatas() {
         const ret = ReactiveCache.getTeams({}, {sort: { teamDisplayName: 1 }});
         return ret;
       },
    -});
    +  events() {
    +    return [
    +      {
    +        'click #cancelBtn': function(){
    +          let selectedElt = document.getElementById("jsteamsUser");
    +          document.getElementById("divAddOrRemoveTeamContainer").style.display = 'none';
    +        },
    +        'click #addTeamBtn': function(){
    +          let selectedElt;
    +          let selectedEltValue;
    +          let selectedEltValueId;
    +          let userTms = [];
    +          let currentUser;
    +          let currUserTeamIndex;
     
    -Template.modifyTeamsUsers.events({
    -  'click #cancelBtn': function(){
    -    let selectedElt = document.getElementById("jsteamsUser");
    -    document.getElementById("divAddOrRemoveTeamContainer").style.display = 'none';
    +          selectedElt = document.getElementById("jsteamsUser");
    +          selectedEltValue = selectedElt.options[selectedElt.selectedIndex].text;
    +          selectedEltValueId = selectedElt.options[selectedElt.selectedIndex].value;
    +
    +          if(document.getElementById('addAction').checked){
    +            for(let i = 0; i < selectedUserChkBoxUserIds.length; i++){
    +              currentUser = ReactiveCache.getUser(selectedUserChkBoxUserIds[i]);
    +              userTms = currentUser.teams;
    +              if(userTms == undefined || userTms.length == 0){
    +                userTms = [];
    +                userTms.push({
    +                  "teamId": selectedEltValueId,
    +                  "teamDisplayName": selectedEltValue,
    +                })
    +              }
    +              else if(userTms.length > 0)
    +              {
    +                currUserTeamIndex = userTms.findIndex(function(t){ return t.teamId == selectedEltValueId});
    +                if(currUserTeamIndex == -1){
    +                  userTms.push({
    +                    "teamId": selectedEltValueId,
    +                    "teamDisplayName": selectedEltValue,
    +                  });
    +                }
    +              }
    +
    +              Users.update(selectedUserChkBoxUserIds[i], {
    +                $set:{
    +                  teams: userTms
    +                }
    +              });
    +            }
    +          }
    +          else{
    +            for(let i = 0; i < selectedUserChkBoxUserIds.length; i++){
    +              currentUser = ReactiveCache.getUser(selectedUserChkBoxUserIds[i]);
    +              userTms = currentUser.teams;
    +              if(userTms !== undefined || userTms.length > 0)
    +              {
    +                currUserTeamIndex = userTms.findIndex(function(t){ return t.teamId == selectedEltValueId});
    +                if(currUserTeamIndex != -1){
    +                  userTms.splice(currUserTeamIndex, 1);
    +                }
    +              }
    +
    +              Users.update(selectedUserChkBoxUserIds[i], {
    +                $set:{
    +                  teams: userTms
    +                }
    +              });
    +            }
    +          }
    +
    +          document.getElementById("divAddOrRemoveTeamContainer").style.display = 'none';
    +        },
    +      },
    +    ];
       },
    -  'click #addTeamBtn': function(){
    -    let selectedElt;
    -    let selectedEltValue;
    -    let selectedEltValueId;
    -    let userTms = [];
    -    let currentUser;
    -    let currUserTeamIndex;
    +}).register('modifyTeamsUsers');
     
    -    selectedElt = document.getElementById("jsteamsUser");
    -    selectedEltValue = selectedElt.options[selectedElt.selectedIndex].text;
    -    selectedEltValueId = selectedElt.options[selectedElt.selectedIndex].value;
    -
    -    if(document.getElementById('addAction').checked){
    -      for(let i = 0; i < selectedUserChkBoxUserIds.length; i++){
    -        currentUser = ReactiveCache.getUser(selectedUserChkBoxUserIds[i]);
    -        userTms = currentUser.teams;
    -        if(userTms == undefined || userTms.length == 0){
    -          userTms = [];
    -          userTms.push({
    -            "teamId": selectedEltValueId,
    -            "teamDisplayName": selectedEltValue,
    -          })
    -        }
    -        else if(userTms.length > 0)
    -        {
    -          currUserTeamIndex = userTms.findIndex(function(t){ return t.teamId == selectedEltValueId});
    -          if(currUserTeamIndex == -1){
    -            userTms.push({
    -              "teamId": selectedEltValueId,
    -              "teamDisplayName": selectedEltValue,
    -            });
    -          }
    -        }
    -
    -        Users.update(selectedUserChkBoxUserIds[i], {
    -          $set:{
    -            teams: userTms
    -          }
    -        });
    -      }
    -    }
    -    else{
    -      for(let i = 0; i < selectedUserChkBoxUserIds.length; i++){
    -        currentUser = ReactiveCache.getUser(selectedUserChkBoxUserIds[i]);
    -        userTms = currentUser.teams;
    -        if(userTms !== undefined || userTms.length > 0)
    -        {
    -          currUserTeamIndex = userTms.findIndex(function(t){ return t.teamId == selectedEltValueId});
    -          if(currUserTeamIndex != -1){
    -            userTms.splice(currUserTeamIndex, 1);
    -          }
    -        }
    -
    -        Users.update(selectedUserChkBoxUserIds[i], {
    -          $set:{
    -            teams: userTms
    -          }
    -        });
    -      }
    -    }
    -
    -    document.getElementById("divAddOrRemoveTeamContainer").style.display = 'none';
    +BlazeComponent.extendComponent({
    +  events() {
    +    return [
    +      {
    +        'click a.new-org': Popup.open('newOrg'),
    +      },
    +    ];
       },
    -});
    +}).register('newOrgRow');
     
    -Template.newOrgRow.events({
    -  'click a.new-org': Popup.open('newOrg'),
    -});
    -
    -Template.newTeamRow.events({
    -  'click a.new-team': Popup.open('newTeam'),
    -});
    -
    -Template.newUserRow.events({
    -  'click a.new-user': Popup.open('newUser'),
    -});
    -
    -Template.selectAllUser.events({
    -  'click .allUserChkBox': function(ev){
    -    selectedUserChkBoxUserIds = [];
    -    const checkboxes = document.getElementsByClassName("selectUserChkBox");
    -    if(ev.currentTarget){
    -      if(ev.currentTarget.checked){
    -        for (let i=0; i 0)
    -      document.getElementById("divAddOrRemoveTeam").style.display = 'block';
    -    else
    -      document.getElementById("divAddOrRemoveTeam").style.display = 'none';
    +BlazeComponent.extendComponent({
    +  events() {
    +    return [
    +      {
    +        'click a.new-team': Popup.open('newTeam'),
    +      },
    +    ];
       },
    -});
    +}).register('newTeamRow');
    +
    +BlazeComponent.extendComponent({
    +  events() {
    +    return [
    +      {
    +        'click a.new-user': Popup.open('newUser'),
    +      },
    +    ];
    +  },
    +}).register('newUserRow');
    +
    +BlazeComponent.extendComponent({
    +  events() {
    +    return [
    +      {
    +        'click .allUserChkBox': function(ev){
    +          selectedUserChkBoxUserIds = [];
    +          const checkboxes = document.getElementsByClassName("selectUserChkBox");
    +          if(ev.currentTarget){
    +            if(ev.currentTarget.checked){
    +              for (let i=0; i 0)
    +            document.getElementById("divAddOrRemoveTeam").style.display = 'block';
    +          else
    +            document.getElementById("divAddOrRemoveTeam").style.display = 'none';
    +        },
    +      },
    +    ];
    +  },
    +}).register('selectAllUser');
     
     Template.editOrgPopup.events({
       submit(event, templateInstance) {
    @@ -837,7 +839,16 @@ Template.editUserPopup.events({
             ? user.emails[0].address.toLowerCase()
             : false);
     
    -    // Build user teams list
    +    Users.update(this.userId, {
    +      $set: {
    +        'profile.fullname': fullname,
    +        isAdmin: isAdmin === 'true',
    +        loginDisabled: isActive === 'true',
    +        authenticationMethod: authentication,
    +        importUsernames: Users.parseImportUsernames(importUsernames),
    +      },
    +    });
    +
         let userTeamsList = userTeams.split(",");
         let userTeamsIdsList = userTeamsIds.split(",");
         let userTms = [];
    @@ -850,7 +861,12 @@ Template.editUserPopup.events({
           }
         }
     
    -    // Build user orgs list
    +    Users.update(this.userId, {
    +      $set:{
    +        teams: userTms
    +      }
    +    });
    +
         let userOrgsList = userOrgs.split(",");
         let userOrgsIdsList = userOrgsIds.split(",");
         let userOrganizations = [];
    @@ -863,20 +879,9 @@ Template.editUserPopup.events({
           }
         }
     
    -    // Update user via Meteor method (for admin to edit other users)
    -    const updateData = {
    -      fullname: fullname,
    -      isAdmin: isAdmin === 'true',
    -      loginDisabled: isActive === 'true',
    -      authenticationMethod: authentication,
    -      importUsernames: Users.parseImportUsernames(importUsernames),
    -      teams: userTms,
    -      orgs: userOrganizations,
    -    };
    -
    -    Meteor.call('editUser', this.userId, updateData, (error) => {
    -      if (error) {
    -        console.error('Error updating user:', error);
    +    Users.update(this.userId, {
    +      $set:{
    +        orgs: userOrganizations
           }
         });
     
    diff --git a/client/components/settings/settingBody.css b/client/components/settings/settingBody.css
    index 387bffa85..765baa77c 100644
    --- a/client/components/settings/settingBody.css
    +++ b/client/components/settings/settingBody.css
    @@ -117,24 +117,6 @@
       padding-bottom: 50px;
     }
     
    -/* Admin panel buttons should use theme darker color */
    -.setting-content .content-body .main-body .setting-detail button.btn {
    -  background: #005377;
    -  color: #fff;
    -  border: none;
    -}
    -
    -.setting-content .content-body .main-body .setting-detail button.btn:hover,
    -.setting-content .content-body .main-body .setting-detail button.btn:focus {
    -  background: #004766;
    -  color: #fff;
    -}
    -
    -.setting-content .content-body .main-body .setting-detail button.btn:active {
    -  background: #01628c;
    -  color: #fff;
    -}
    -
     /* Force horizontal scrollbar to always be visible at bottom */
     .setting-content .content-body .main-body {
       position: relative;
    @@ -155,13 +137,8 @@
       padding: 0.5rem 0.5rem;
     }
     .setting-content .content-body .main-body ul li a .is-checked {
    -  border-bottom: 2px solid #3cb500;
    -  border-right: 2px solid #3cb500;
    -}
    -/* Grey checkmarks when grey icons setting is enabled */
    -body.grey-icons-enabled .setting-content .content-body .main-body ul li a .is-checked {
    -  border-bottom: 2px solid #7a7a7a;
    -  border-right: 2px solid #7a7a7a;
    +  border-bottom: 2px solid #2980b9;
    +  border-right: 2px solid #2980b9;
     }
     .setting-content .content-body .main-body ul li a span {
       padding: 0 0.5rem;
    @@ -230,22 +207,3 @@ li.has-error .form-group .wekan-form-control {
       border-color: #a94442;
       box-shadow: inset 0 1px 1px rgba(0,0,0,0.075);
     }
    -
    -/* Constrain textarea elements with balanced left/right spacing */
    -.setting-content .content-body .main-body textarea.wekan-form-control {
    -  width: calc(100% - 20px);
    -  margin: 0 10px;
    -  max-width: calc(100vw - 320px);
    -  box-sizing: border-box;
    -}
    -
    -/* For nested custom head/manifest/assetlinks sections */
    -.setting-content .content-body .main-body .custom-head-settings textarea.wekan-form-control,
    -.setting-content .content-body .main-body .custom-manifest-settings textarea.wekan-form-control,
    -.setting-content .content-body .main-body .custom-assetlinks-settings textarea.wekan-form-control {
    -  width: calc(100% - 20px);
    -  margin-left: 0;
    -  margin-right: 10px;
    -  max-width: calc(100vw - 240px);
    -  box-sizing: border-box;
    -}
    diff --git a/client/components/settings/settingBody.jade b/client/components/settings/settingBody.jade
    index 180f36832..9101e18f7 100644
    --- a/client/components/settings/settingBody.jade
    +++ b/client/components/settings/settingBody.jade
    @@ -6,100 +6,88 @@ template(name="setting")
           .content-title.ext-box
             if isGeneralSetting
               span
    -            span.emoji-icon
    -            i.fa.fa-key
    +            | 🔑
                 | {{_ 'registration'}}
             else if isEmailSetting
               span
    -            span.emoji-icon
    -            i.fa.fa-envelope
    +            | ✉️
                 | {{_ 'email'}}
             else if isAccountSetting
               span
    -            span.emoji-icon
    -            i.fa.fa-users
    +            | 👥
                 | {{_ 'accounts'}}
             else if isTableVisibilityModeSetting
               span
    -            span.emoji-icon
    -            i.fa.fa-eye
    +            | 👁️
                 | {{_ 'tableVisibilityMode'}}
             else if isAnnouncementSetting
               span
    -            span.emoji-icon
    -            i.fa.fa-bullhorn
    +            | 📢
                 | {{_ 'admin-announcement'}}
             else if isAccessibilitySetting
               span
    -            span.emoji-icon
    -            i.fa.fa-universal-access
    +            | ♿
                 | {{_ 'accessibility'}}
             else if isLayoutSetting
               span
    -            span.emoji-icon
    -            i.fa.fa-link
    +            | 🔗
                 | {{_ 'layout'}}
             else if isWebhookSetting
               span
    -            span.emoji-icon
    -            i.fa.fa-globe
    +            | 🌐
                 | {{_ 'global-webhook'}}
             else if isAttachmentSettings
               span
    -            span.emoji-icon
    -            i.fa.fa-paperclip
    +            | 📎
                 | {{_ 'attachments'}}
    -        //- COMMENTED OUT: Migration settings title section
    +        else if isCronSettings
    +          span
    +            | ⏰
    +            | {{_ 'cron'}}
           .content-body
             .side-menu
               ul
                 li(class="{{#if isGeneralSetting}}active{{/if}}")
                   a.js-setting-menu(data-id="registration-setting")
    -                span.emoji-icon
    -                i.fa.fa-key
    +                | 🔑
                     | {{_ 'registration'}}
                 unless isSandstorm
                   li(class="{{#if isEmailSetting}}active{{/if}}")
                     a.js-setting-menu(data-id="email-setting")
    -                  span.emoji-icon
    -                  i.fa.fa-envelope
    +                  | ✉️
                       | {{_ 'email'}}
                 li(class="{{#if isAccountSetting}}active{{/if}}")
                   a.js-setting-menu(data-id="account-setting")
    -                span.emoji-icon
    -                i.fa.fa-users
    +                | 👥
                     | {{_ 'accounts'}}
                 li(class="{{#if isTableVisibilityModeSetting}}active{{/if}}")
                   a.js-setting-menu(data-id="tableVisibilityMode-setting")
    -                span.emoji-icon
    -                i.fa.fa-eye
    +                | 👁️
                     | {{_ 'tableVisibilityMode'}}
                 li(class="{{#if isAnnouncementSetting}}active{{/if}}")
                   a.js-setting-menu(data-id="announcement-setting")
    -                span.emoji-icon
    -                i.fa.fa-bullhorn
    +                | 📢
                     | {{_ 'admin-announcement'}}
                 li(class="{{#if isAccessibilitySetting}}active{{/if}}")
                   a.js-setting-menu(data-id="accessibility-setting")
    -                span.emoji-icon
    -                i.fa.fa-universal-access
    +                | ♿
                     | {{_ 'accessibility'}}
                 li(class="{{#if isLayoutSetting}}active{{/if}}")
                   a.js-setting-menu(data-id="layout-setting")
    -                span.emoji-icon
    -                i.fa.fa-link
    +                | 🔗
                     | {{_ 'layout'}}
                 li(class="{{#if isWebhookSetting}}active{{/if}}")
                   a.js-setting-menu(data-id="webhook-setting")
    -                span.emoji-icon
    -                i.fa.fa-globe
    +                | 🌐
                     | {{_ 'global-webhook'}}
                 li(class="{{#if isAttachmentSettings}}active{{/if}}")
                   a.js-setting-menu(data-id="attachment-settings")
    -                span.emoji-icon
    -                i.fa.fa-paperclip
    +                | 📎
                     | {{_ 'attachments'}}
    -            //- COMMENTED OUT: Migration menu item
    +            li(class="{{#if isCronSettings}}active{{/if}}")
    +              a.js-setting-menu(data-id="cron-settings")
    +                | ⏰
    +                | {{_ 'cron'}}
             .main-body
               if isLoading
                 +spinner
    @@ -111,12 +99,12 @@ template(name="setting")
                       label {{_ 'writable-path'}}
                       input.wekan-form-control#filesystem-path(type="text" value="{{filesystemPath}}" readonly)
                       small.form-text.text-muted {{_ 'filesystem-path-description'}}
    -
    +                
                     .form-group
                       label {{_ 'attachments-path'}}
                       input.wekan-form-control#attachments-path(type="text" value="{{attachmentsPath}}" readonly)
                       small.form-text.text-muted {{_ 'attachments-path-description'}}
    -
    +                
                     .form-group
                       label {{_ 'avatars-path'}}
                       input.wekan-form-control#avatars-path(type="text" value="{{avatarsPath}}" readonly)
    @@ -135,45 +123,89 @@ template(name="setting")
                       label {{_ 's3-enabled'}}
                       input.wekan-form-control#s3-enabled(type="checkbox" checked="{{s3Enabled}}" disabled)
                       small.form-text.text-muted {{_ 's3-enabled-description'}}
    -
    +                
                     .form-group
                       label {{_ 's3-endpoint'}}
                       input.wekan-form-control#s3-endpoint(type="text" value="{{s3Endpoint}}" readonly)
                       small.form-text.text-muted {{_ 's3-endpoint-description'}}
    -
    +                
                     .form-group
                       label {{_ 's3-bucket'}}
                       input.wekan-form-control#s3-bucket(type="text" value="{{s3Bucket}}" readonly)
                       small.form-text.text-muted {{_ 's3-bucket-description'}}
    -
    +                
                     .form-group
                       label {{_ 's3-region'}}
                       input.wekan-form-control#s3-region(type="text" value="{{s3Region}}" readonly)
                       small.form-text.text-muted {{_ 's3-region-description'}}
    -
    +                
                     .form-group
                       label {{_ 's3-access-key'}}
                       input.wekan-form-control#s3-access-key(type="text" placeholder="{{_ 's3-access-key-placeholder'}}" readonly)
                       small.form-text.text-muted {{_ 's3-access-key-description'}}
    -
    +                
                     .form-group
                       label {{_ 's3-secret-key'}}
                       input.wekan-form-control#s3-secret-key(type="password" placeholder="{{_ 's3-secret-key-placeholder'}}")
                       small.form-text.text-muted {{_ 's3-secret-key-description'}}
    -
    +                
                     .form-group
                       label {{_ 's3-ssl-enabled'}}
                       input.wekan-form-control#s3-ssl-enabled(type="checkbox" checked="{{s3SslEnabled}}" disabled)
                       small.form-text.text-muted {{_ 's3-ssl-enabled-description'}}
    -
    +                
                     .form-group
                       label {{_ 's3-port'}}
                       input.wekan-form-control#s3-port(type="number" value="{{s3Port}}" readonly)
                       small.form-text.text-muted {{_ 's3-port-description'}}
    -
    +                
                     .form-group
                       button.js-test-s3-connection.btn.btn-secondary {{_ 'test-s3-connection'}}
                       button.js-save-s3-settings.btn.btn-primary {{_ 'save-s3-settings'}}
    +          else if isCronSettings
    +            ul#cron-setting.setting-detail
    +              li
    +                h3 {{_ 'cron-migrations'}}
    +                .form-group
    +                  label {{_ 'migration-status'}}
    +                  .status-indicator
    +                    span.status-label {{_ 'status'}}:
    +                    span.status-value {{migrationStatus}}
    +                  .progress-section
    +                    .progress
    +                      .progress-bar(role="progressbar" style="width: {{migrationProgress}}%" aria-valuenow="{{migrationProgress}}" aria-valuemin="0" aria-valuemax="100") 
    +                        | {{migrationProgress}}%
    +                    .progress-text
    +                      | {{migrationProgress}}% {{_ 'complete'}}
    +                
    +                .form-group
    +                  button.js-start-all-migrations.btn.btn-primary {{_ 'start-all-migrations'}}
    +                  button.js-pause-all-migrations.btn.btn-warning {{_ 'pause-all-migrations'}}
    +                  button.js-stop-all-migrations.btn.btn-danger {{_ 'stop-all-migrations'}}
    +              
    +              li
    +                h3 {{_ 'board-operations'}}
    +                .form-group
    +                  label {{_ 'scheduled-board-operations'}}
    +                  button.js-schedule-board-cleanup.btn.btn-primary {{_ 'schedule-board-cleanup'}}
    +                  button.js-schedule-board-archive.btn.btn-warning {{_ 'schedule-board-archive'}}
    +                  button.js-schedule-board-backup.btn.btn-info {{_ 'schedule-board-backup'}}
    +              
    +              li
    +                h3 {{_ 'cron-jobs'}}
    +                .form-group
    +                  label {{_ 'active-cron-jobs'}}
    +                  each cronJobs
    +                    .job-item
    +                      .job-info
    +                        .job-name {{name}}
    +                        .job-schedule {{schedule}}
    +                        .job-description {{description}}
    +                      .job-actions
    +                        button.js-pause-job.btn.btn-sm.btn-warning(data-job-id="{{_id}}") {{_ 'pause'}}
    +                        button.js-delete-job.btn.btn-sm.btn-danger(data-job-id="{{_id}}") {{_ 'delete'}}
    +                  .add-job-section
    +                    button.js-add-cron-job.btn.btn-success {{_ 'add-cron-job'}}
               else if isGeneralSetting
                 +general
               else if isEmailSetting
    @@ -229,69 +261,39 @@ template(name="general")
     template(name='email')
       ul#email-setting.setting-detail
         //if isSandstorm
    +    //  li.smtp-form
    +    //    .title {{_ 'smtp-host'}}
    +    //    .description {{_ 'smtp-host-description'}}
    +    //    .form-group
    +    //      input.wekan-form-control#mail-server-host(type="text", placeholder="smtp.domain.com" value="{{currentSetting.mailServer.host}}")
    +    //  li.smtp-form
    +    //    .title {{_ 'smtp-port'}}
    +    //    .description {{_ 'smtp-port-description'}}
    +    //    .form-group
    +    //      input.wekan-form-control#mail-server-port(type="text", placeholder="25" value="{{currentSetting.mailServer.port}}")
    +    //  li.smtp-form
    +    //    .title {{_ 'smtp-username'}}
    +    //    .form-group
    +    //      input.wekan-form-control#mail-server-u"accounts-allowUserNameChange": "Allow Username Change",sername(type="text", placeholder="{{_ 'username'}}" value="{{currentSetting.mailServer.username}}")
    +    //  li.smtp-form
    +    //    .title {{_ 'smtp-password'}}
    +    //    .form-group
    +    //      input.wekan-form-control#mail-server-password(type="password", placeholder="{{_ 'password'}}" value="")
    +    //  li.smtp-form
    +    //    .title {{_ 'smtp-tls'}}
    +    //    .form-group
    +    //      a.flex.js-toggle-tls
    +    //        .materialCheckBox#mail-server-tls(class="{{#if currentSetting.mailServer.enableTLS}}is-checked{{/if}}")
         //
    -      li.smtp-form
    +    //        span {{_ 'smtp-tls-description'}}
         //
    -        .title {{_ 'smtp-host'}}
    +    //  li.smtp-form
    +    //    .title {{_ 'send-from'}}
    +    //    .form-group
    +    //      input.wekan-form-control#mail-server-from(type="email", placeholder="no-reply@domain.com" value="{{currentSetting.mailServer.from}}")
         //
    -        .description {{_ 'smtp-host-description'}}
    -    //
    -        .form-group
    -    //
    -          input.wekan-form-control#mail-server-host(type="text", placeholder="smtp.domain.com" value="{{currentSetting.mailServer.host}}")
    -    //
    -      li.smtp-form
    -    //
    -        .title {{_ 'smtp-port'}}
    -    //
    -        .description {{_ 'smtp-port-description'}}
    -    //
    -        .form-group
    -    //
    -          input.wekan-form-control#mail-server-port(type="text", placeholder="25" value="{{currentSetting.mailServer.port}}")
    -    //
    -      li.smtp-form
    -    //
    -        .title {{_ 'smtp-username'}}
    -    //
    -        .form-group
    -    //
    -          input.wekan-form-control#mail-server-username(type="text", placeholder="{{_ 'username'}}" value="{{currentSetting.mailServer.username}}")
    -    //
    -      li.smtp-form
    -    //
    -        .title {{_ 'smtp-password'}}
    -    //
    -        .form-group
    -    //
    -          input.wekan-form-control#mail-server-password(type="password", placeholder="{{_ 'password'}}" value="")
    -    //
    -      li.smtp-form
    -    //
    -        .title {{_ 'smtp-tls'}}
    -    //
    -        .form-group
    -    //
    -          a.flex.js-toggle-tls
    -    //
    -            .materialCheckBox#mail-server-tls(class="{{#if currentSetting.mailServer.enableTLS}}is-checked{{/if}}")
    -    //
    -    //
    -            span {{_ 'smtp-tls-description'}}
    -    //
    -    //
    -      li.smtp-form
    -    //
    -        .title {{_ 'send-from'}}
    -    //
    -        .form-group
    -    //
    -          input.wekan-form-control#mail-server-from(type="email", placeholder="no-reply@domain.com" value="{{currentSetting.mailServer.from}}")
    -    //
    -    //
    -      li
    -    //
    -        button.js-save.primary {{_ 'save'}}
    +    //  li
    +    //    button.js-save.primary {{_ 'save'}}
     
         li
           button.js-send-smtp-test-email.primary {{_ 'send-smtp-test'}}
    @@ -373,64 +375,6 @@ template(name='layoutSettings')
       ul#layout-setting.setting-detail
         li
           button.js-all-boards-hide-activities.primary {{_ 'hide-activities-of-all-boards'}}
    -    li
    -      a(href="/support" style="text-decoration: underline; color: blue;") {{_ 'support'}}
    -    li
    -      a.flex.js-toggle-support
    -        .materialCheckBox(class="{{#if currentSetting.supportPageEnabled}}is-checked{{/if}}")
    -
    -        span {{_ 'support-page-enabled'}}
    -    li
    -      .support-content(class="{{#if currentSetting.supportPageEnabled}}{{else}}hide{{/if}}")
    -        ul
    -          li
    -            a.flex.js-toggle-support-public
    -              .materialCheckBox(class="{{#if currentSetting.supportPagePublic}}is-checked{{/if}}")
    -
    -              span {{_ 'public'}}
    -          li
    -            .title {{_ 'support-title'}}
    -            textarea#support-title.wekan-form-control= currentSetting.supportTitle
    -          li
    -            .title {{_ 'support-content'}}
    -            textarea#support-page-text.wekan-form-control= currentSetting.supportPageText
    -          li
    -            button.js-support-save.primary {{_ 'save'}}
    -    li
    -      a.flex.js-toggle-custom-head
    -        .materialCheckBox(class="{{#if currentSetting.customHeadEnabled}}is-checked{{/if}}")
    -        span {{_ 'custom-head-tags-enabled'}}
    -    li
    -      .custom-head-settings(class="{{#if currentSetting.customHeadEnabled}}{{else}}hide{{/if}}")
    -        ul
    -          li
    -            .title {{_ 'custom-head-meta-tags'}}
    -            textarea#custom-head-meta.wekan-form-control= customHeadMetaTagsValue
    -          li
    -            .title {{_ 'custom-head-link-tags'}}
    -            textarea#custom-head-links.wekan-form-control= customHeadLinkTagsValue
    -          li
    -            a.flex.js-toggle-custom-manifest
    -              .materialCheckBox(class="{{#if currentSetting.customManifestEnabled}}is-checked{{/if}}")
    -              span {{_ 'custom-manifest-enabled'}}
    -          li
    -            .custom-manifest-settings(class="{{#if currentSetting.customManifestEnabled}}{{else}}hide{{/if}}")
    -              .title {{_ 'custom-head-manifest-content'}}
    -              textarea#custom-manifest-content.wekan-form-control= customManifestContentValue
    -          li
    -            button.js-custom-head-save.primary {{_ 'save'}}
    -    li
    -      a.flex.js-toggle-custom-assetlinks
    -        .materialCheckBox(class="{{#if currentSetting.customAssetLinksEnabled}}is-checked{{/if}}")
    -        span {{_ 'custom-assetlinks-enabled'}}
    -    li
    -      .custom-assetlinks-settings(class="{{#if currentSetting.customAssetLinksEnabled}}{{else}}hide{{/if}}")
    -        ul
    -          li
    -            .title {{_ 'custom-assetlinks-content'}}
    -            textarea#custom-assetlinks-content.wekan-form-control= customAssetLinksContentValue
    -          li
    -            button.js-custom-assetlinks-save.primary {{_ 'save'}}
         li.layout-form
           .title {{_ 'oidc-button-text'}}
           .form-group
    diff --git a/client/components/settings/settingBody.js b/client/components/settings/settingBody.js
    index e3214eca0..682479c92 100644
    --- a/client/components/settings/settingBody.js
    +++ b/client/components/settings/settingBody.js
    @@ -2,761 +2,132 @@ import { ReactiveCache } from '/imports/reactiveCache';
     import { TAPi18n } from '/imports/i18n';
     import { ALLOWED_WAIT_SPINNERS } from '/config/const';
     import LockoutSettings from '/models/lockoutSettings';
    -// import {
    -//   cronMigrationProgress,
    -//   cronMigrationStatus,
    -//   cronMigrationCurrentStep,
    -//   cronMigrationSteps,
    -//   cronIsMigrating,
    -//   cronJobs,
    -//   cronMigrationCurrentStepNum,
    -//   cronMigrationTotalSteps,
    -//   cronMigrationCurrentAction,
    -//   cronMigrationJobProgress,
    -//   cronMigrationJobStepNum,
    -//   cronMigrationJobTotalSteps,
    -//   cronMigrationEtaSeconds,
    -//   cronMigrationElapsedSeconds,
    -//   cronMigrationCurrentNumber,
    -//   cronMigrationCurrentName,
    -// } from '/imports/cronMigrationClient';
    -import { format } from '/imports/lib/dateUtils';
     
    -// Helper functions shared across the template
    -function checkField(selector) {
    -  const value = $(selector).val();
    -  if (!value || value.trim() === '') {
    -    $(selector).parents('li.smtp-form').addClass('has-error');
    -    throw Error('blank field');
    -  } else {
    -    return value;
    -  }
    -}
     
    -function cleanAndValidateJSON(content) {
    -  if (!content || !content.trim()) {
    -    return { json: content };
    -  }
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.error = new ReactiveVar('');
    +    this.loading = new ReactiveVar(false);
    +    this.forgotPasswordSetting = new ReactiveVar(false);
    +    this.generalSetting = new ReactiveVar(true);
    +    this.emailSetting = new ReactiveVar(false);
    +    this.accountSetting = new ReactiveVar(false);
    +    this.tableVisibilityModeSetting = new ReactiveVar(false);
    +    this.announcementSetting = new ReactiveVar(false);
    +    this.accessibilitySetting = new ReactiveVar(false);
    +    this.layoutSetting = new ReactiveVar(false);
    +    this.webhookSetting = new ReactiveVar(false);
    +    this.attachmentSettings = new ReactiveVar(false);
    +    this.cronSettings = new ReactiveVar(false);
     
    -  try {
    -    // Try to parse as-is
    -    const parsed = JSON.parse(content);
    -    return { json: JSON.stringify(parsed, null, 2) };
    -  } catch (e) {
    -    const errorMsg = e.message;
    +    Meteor.subscribe('setting');
    +    Meteor.subscribe('mailServer');
    +    Meteor.subscribe('accountSettings');
    +    Meteor.subscribe('tableVisibilityModeSettings');
    +    Meteor.subscribe('announcements');
    +    Meteor.subscribe('accessibilitySettings');
    +    Meteor.subscribe('globalwebhooks');
    +    Meteor.subscribe('lockoutSettings');
    +  },
     
    -    // If error is "unexpected non-whitespace character after JSON data"
    -    if (
    -      errorMsg.includes('unexpected non-whitespace character after JSON data')
    -    ) {
    -      try {
    -        // Try to find and extract valid JSON by finding matching braces/brackets
    -        const trimmed = content.trim();
    -        let depth = 0;
    -        let endPos = -1;
    -        let inString = false;
    -        let escapeNext = false;
     
    -        for (let i = 0; i < trimmed.length; i++) {
    -          const char = trimmed[i];
    -
    -          if (escapeNext) {
    -            escapeNext = false;
    -            continue;
    -          }
    -
    -          if (char === '\\') {
    -            escapeNext = true;
    -            continue;
    -          }
    -
    -          if (char === '"' && !escapeNext) {
    -            inString = !inString;
    -            continue;
    -          }
    -
    -          if (inString) continue;
    -
    -          if (char === '{' || char === '[') {
    -            depth++;
    -          } else if (char === '}' || char === ']') {
    -            depth--;
    -            if (depth === 0) {
    -              endPos = i + 1;
    -              break;
    -            }
    -          }
    -        }
    -
    -        if (endPos > 0) {
    -          const cleanedContent = trimmed.substring(0, endPos);
    -          const parsed = JSON.parse(cleanedContent);
    -          return { json: JSON.stringify(parsed, null, 2) };
    -        }
    -      } catch (fixError) {
    -        // If fix attempt fails, return original error
    -      }
    -    }
    -
    -    // Remove trailing commas (common error)
    -    if (errorMsg.includes('Unexpected token')) {
    -      try {
    -        const fixed = content.replace(/,(\s*[}\]])/g, '$1');
    -        const parsed = JSON.parse(fixed);
    -        return { json: JSON.stringify(parsed, null, 2) };
    -      } catch (fixError) {
    -        // Continue to error return
    -      }
    -    }
    -
    -    return { error: errorMsg };
    -  }
    -}
    -
    -Template.setting.onCreated(function () {
    -  this.error = new ReactiveVar('');
    -  this.loading = new ReactiveVar(false);
    -  this.forgotPasswordSetting = new ReactiveVar(false);
    -  this.generalSetting = new ReactiveVar(true);
    -  this.emailSetting = new ReactiveVar(false);
    -  this.accountSetting = new ReactiveVar(false);
    -  this.tableVisibilityModeSetting = new ReactiveVar(false);
    -  this.announcementSetting = new ReactiveVar(false);
    -  this.accessibilitySetting = new ReactiveVar(false);
    -  this.layoutSetting = new ReactiveVar(false);
    -  this.webhookSetting = new ReactiveVar(false);
    -  this.attachmentSettings = new ReactiveVar(false);
    -  // this.cronSettings = new ReactiveVar(false);
    -  // this.migrationErrorsList = new ReactiveVar([]);
    -
    -  Meteor.subscribe('setting');
    -  Meteor.subscribe('mailServer');
    -  Meteor.subscribe('accountSettings');
    -  Meteor.subscribe('tableVisibilityModeSettings');
    -  Meteor.subscribe('announcements');
    -  Meteor.subscribe('accessibilitySettings');
    -  Meteor.subscribe('globalwebhooks');
    -  Meteor.subscribe('lockoutSettings');
    -
    -  // Poll for migration errors
    -  // this.errorPollInterval = Meteor.setInterval(() => {
    -  //   if (this.cronSettings.get()) {
    -  //     Meteor.call('cron.getAllMigrationErrors', 50, (error, result) => {
    -  //       if (!error && result) {
    -  //         this.migrationErrorsList.set(result);
    -  //       }
    -  //     });
    -  //   }
    -  // }, 5000); // Poll every 5 seconds
    -});
    -
    -Template.setting.onDestroyed(function () {
    -  // if (this.errorPollInterval) {
    -  //   Meteor.clearInterval(this.errorPollInterval);
    -  // }
    -});
    -
    -Template.setting.helpers({
    +  setError(error) {
    +    this.error.set(error);
    +  },
    +  
    +  // Template helpers moved to BlazeComponent - using different names to avoid conflicts
       isGeneralSetting() {
    -    const inst = Template.instance();
    -    return inst.generalSetting && inst.generalSetting.get();
    +    return this.generalSetting && this.generalSetting.get();
       },
       isEmailSetting() {
    -    const inst = Template.instance();
    -    return inst.emailSetting && inst.emailSetting.get();
    +    return this.emailSetting && this.emailSetting.get();
       },
       isAccountSetting() {
    -    const inst = Template.instance();
    -    return inst.accountSetting && inst.accountSetting.get();
    +    return this.accountSetting && this.accountSetting.get();
       },
       isTableVisibilityModeSetting() {
    -    const inst = Template.instance();
    -    return (
    -      inst.tableVisibilityModeSetting && inst.tableVisibilityModeSetting.get()
    -    );
    +    return this.tableVisibilityModeSetting && this.tableVisibilityModeSetting.get();
       },
       isAnnouncementSetting() {
    -    const inst = Template.instance();
    -    return inst.announcementSetting && inst.announcementSetting.get();
    +    return this.announcementSetting && this.announcementSetting.get();
       },
       isAccessibilitySetting() {
    -    const inst = Template.instance();
    -    return inst.accessibilitySetting && inst.accessibilitySetting.get();
    +    return this.accessibilitySetting && this.accessibilitySetting.get();
       },
       isLayoutSetting() {
    -    const inst = Template.instance();
    -    return inst.layoutSetting && inst.layoutSetting.get();
    +    return this.layoutSetting && this.layoutSetting.get();
       },
       isWebhookSetting() {
    -    const inst = Template.instance();
    -    return inst.webhookSetting && inst.webhookSetting.get();
    +    return this.webhookSetting && this.webhookSetting.get();
       },
       isAttachmentSettings() {
    -    const inst = Template.instance();
    -    return inst.attachmentSettings && inst.attachmentSettings.get();
    +    return this.attachmentSettings && this.attachmentSettings.get();
    +  },
    +  isCronSettings() {
    +    return this.cronSettings && this.cronSettings.get();
       },
    -  // isCronSettings() {
    -  //   const inst = Template.instance();
    -  //   return inst.cronSettings && inst.cronSettings.get();
    -  // },
       isLoading() {
    -    const inst = Template.instance();
    -    return inst.loading && inst.loading.get();
    +    return this.loading && this.loading.get();
       },
     
       // Attachment settings helpers
       filesystemPath() {
         return process.env.WRITABLE_PATH || '/data';
       },
    -
    +  
       attachmentsPath() {
         const writablePath = process.env.WRITABLE_PATH || '/data';
         return `${writablePath}/attachments`;
       },
    -
    +  
       avatarsPath() {
         const writablePath = process.env.WRITABLE_PATH || '/data';
         return `${writablePath}/avatars`;
       },
    -
    +  
       gridfsEnabled() {
         return process.env.GRIDFS_ENABLED === 'true';
       },
    -
    +  
       s3Enabled() {
         return process.env.S3_ENABLED === 'true';
       },
    -
    +  
       s3Endpoint() {
         return process.env.S3_ENDPOINT || '';
       },
    -
    +  
       s3Bucket() {
         return process.env.S3_BUCKET || '';
       },
    -
    +  
       s3Region() {
         return process.env.S3_REGION || '';
       },
    -
    +  
       s3SslEnabled() {
         return process.env.S3_SSL_ENABLED === 'true';
       },
    -
    +  
       s3Port() {
         return process.env.S3_PORT || 443;
       },
     
       // Cron settings helpers
    -  // migrationStatus() {
    -  //   return cronMigrationStatus.get() || TAPi18n.__('idle');
    -  // },
    -  //
    -  // migrationProgress() {
    -  //   return cronMigrationProgress.get() || 0;
    -  // },
    -  //
    -  // migrationCurrentStep() {
    -  //   return cronMigrationCurrentStep.get() || '';
    -  // },
    -  //
    -  // isMigrating() {
    -  //   return cronIsMigrating.get() || false;
    -  // },
    -  //
    -  // migrationSteps() {
    -  //   return cronMigrationSteps.get() || [];
    -  // },
    -  //
    -  // migrationStepsWithIndex() {
    -  //   const steps = cronMigrationSteps.get() || [];
    -  //   return steps.map((step, idx) => ({
    -  //     ...step,
    -  //     index: idx + 1,
    -  //   }));
    -  // },
    -  //
    -  // cronJobs() {
    -  //   return cronJobs.get() || [];
    -  // },
    -  //
    -  // isCronJobPaused(status) {
    -  //   return status === 'paused';
    -  // },
    -  //
    -  // migrationCurrentStepNum() {
    -  //   return cronMigrationCurrentStepNum.get() || 0;
    -  // },
    -  //
    -  // migrationTotalSteps() {
    -  //   return cronMigrationTotalSteps.get() || 0;
    -  // },
    -  //
    -  // migrationCurrentAction() {
    -  //   return cronMigrationCurrentAction.get() || '';
    -  // },
    -  //
    -  // migrationJobProgress() {
    -  //   return cronMigrationJobProgress.get() || 0;
    -  // },
    -  //
    -  // migrationJobStepNum() {
    -  //   return cronMigrationJobStepNum.get() || 0;
    -  // },
    -  //
    -  // migrationJobTotalSteps() {
    -  //   return cronMigrationJobTotalSteps.get() || 0;
    -  // },
    -  //
    -  // migrationEtaSeconds() {
    -  //   return cronMigrationEtaSeconds.get();
    -  // },
    -  //
    -  // migrationElapsedSeconds() {
    -  //   return cronMigrationElapsedSeconds.get();
    -  // },
    -  //
    -  // migrationNumber() {
    -  //   return cronMigrationCurrentNumber.get();
    -  // },
    -  //
    -  // migrationName() {
    -  //   return cronMigrationCurrentName.get() || '';
    -  // },
    -  //
    -  // migrationStatusLine() {
    -  //   const number = cronMigrationCurrentNumber.get();
    -  //   const name = cronMigrationCurrentName.get();
    -  //   if (number && name) {
    -  //     return `${number} - ${name}`;
    -  //   }
    -  //   return cronMigrationStatus.get() || TAPi18n.__('idle');
    -  // },
    -  //
    -  // isUpdatingMigrationDropdown() {
    -  //   const status = cronMigrationStatus.get() || TAPi18n.__('idle');
    -  //   return (
    -  //     status && status.startsWith('Updating Select Migration dropdown menu')
    -  //   );
    -  // },
    -  //
    -  // migrationErrors() {
    -  //   return Template.instance().migrationErrorsList ? Template.instance().migrationErrorsList.get() : [];
    -  // },
    -  //
    -  // hasErrors() {
    -  //   const inst = Template.instance();
    -  //   const errors = inst.migrationErrorsList ? inst.migrationErrorsList.get() : [];
    -  //   return errors && errors.length > 0;
    -  // },
    -  //
    -  // formatDateTime(date) {
    -  //   if (!date) return '';
    -  //   return format(date, 'YYYY-MM-DD HH:mm:ss');
    -  // },
    -  //
    -  // formatDurationSeconds(seconds) {
    -  //   if (seconds === null || seconds === undefined) return '';
    -  //   const total = Math.max(0, Math.floor(seconds));
    -  //   const hrs = Math.floor(total / 3600);
    -  //   const mins = Math.floor((total % 3600) / 60);
    -  //   const secs = total % 60;
    -  //   const parts = [];
    -  //   if (hrs > 0) parts.push(String(hrs).padStart(2, '0'));
    -  //   parts.push(String(mins).padStart(2, '0'));
    -  //   parts.push(String(secs).padStart(2, '0'));
    -  //   return parts.join(':');
    -  // },
    +  migrationStatus() {
    +    return TAPi18n.__('idle'); // Placeholder
    +  },
    +  
    +  migrationProgress() {
    +    return 0; // Placeholder
    +  },
    +  
    +  cronJobs() {
    +    return []; // Placeholder
    +  },
     
    -  boards() {
    -    const ret = ReactiveCache.getBoards(
    -      {
    -        archived: false,
    -        'members.userId': Meteor.userId(),
    -        'members.isAdmin': true,
    -      },
    -      {
    -        sort: { sort: 1 /* boards default sorting */ },
    -      },
    -    );
    -    return ret;
    -  },
    -});
    -
    -Template.setting.events({
    -  'click a.js-toggle-forgot-password'(event, tpl) {
    -    tpl.loading.set(true);
    -    const forgotPasswordClosed =
    -      ReactiveCache.getCurrentSetting().disableForgotPassword;
    -    Settings.update(ReactiveCache.getCurrentSetting()._id, {
    -      $set: { disableForgotPassword: !forgotPasswordClosed },
    -    });
    -    tpl.loading.set(false);
    -  },
    -  'click a.js-toggle-registration'(event, tpl) {
    -    tpl.loading.set(true);
    -    const registrationClosed =
    -      ReactiveCache.getCurrentSetting().disableRegistration;
    -    Settings.update(ReactiveCache.getCurrentSetting()._id, {
    -      $set: { disableRegistration: !registrationClosed },
    -    });
    -    tpl.loading.set(false);
    -    if (registrationClosed) {
    -      $('.invite-people').slideUp();
    -    } else {
    -      $('.invite-people').slideDown();
    -    }
    -  },
    -  'click a.js-toggle-tls'() {
    -    $('#mail-server-tls').toggleClass('is-checked');
    -  },
    -  'click a.js-toggle-hide-logo'() {
    -    $('#hide-logo').toggleClass('is-checked');
    -  },
    -  'click a.js-toggle-hide-card-counter-list'() {
    -    $('#hide-card-counter-list').toggleClass('is-checked');
    -  },
    -  'click a.js-toggle-hide-board-member-list'() {
    -    $('#hide-board-member-list').toggleClass('is-checked');
    -  },
    -  'click a.js-toggle-display-authentication-method'() {
    -    $('#display-authentication-method').toggleClass('is-checked');
    -  },
    -  'click a.js-setting-menu'(event, tpl) {
    -    const target = $(event.target);
    -    if (!target.hasClass('active')) {
    -      $('.side-menu li.active').removeClass('active');
    -      target.parent().addClass('active');
    -      const targetID = target.data('id');
    -
    -      // Reset all settings to false
    -      tpl.forgotPasswordSetting.set(false);
    -      tpl.generalSetting.set(false);
    -      tpl.emailSetting.set(false);
    -      tpl.accountSetting.set(false);
    -      tpl.tableVisibilityModeSetting.set(false);
    -      tpl.announcementSetting.set(false);
    -      tpl.accessibilitySetting.set(false);
    -      tpl.layoutSetting.set(false);
    -      tpl.webhookSetting.set(false);
    -      tpl.attachmentSettings.set(false);
    -      // tpl.cronSettings.set(false);
    -
    -      // Set the selected setting to true
    -      if (targetID === 'registration-setting') {
    -        tpl.generalSetting.set(true);
    -      } else if (targetID === 'email-setting') {
    -        tpl.emailSetting.set(true);
    -      } else if (targetID === 'account-setting') {
    -        tpl.accountSetting.set(true);
    -      } else if (targetID === 'tableVisibilityMode-setting') {
    -        tpl.tableVisibilityModeSetting.set(true);
    -      } else if (targetID === 'announcement-setting') {
    -        tpl.announcementSetting.set(true);
    -      } else if (targetID === 'accessibility-setting') {
    -        tpl.accessibilitySetting.set(true);
    -      } else if (targetID === 'layout-setting') {
    -        tpl.layoutSetting.set(true);
    -      } else if (targetID === 'webhook-setting') {
    -        tpl.webhookSetting.set(true);
    -      } else if (targetID === 'attachment-settings') {
    -        tpl.attachmentSettings.set(true);
    -        // Set default sub-menu state for attachment settings
    -        console.log('Initializing attachment sub-menu');
    -      } // else if (targetID === 'cron-settings') {
    -      //   tpl.cronSettings.set(true);
    -      //   console.log('Initializing cron sub-menu');
    -      // }
    -    }
    -  },
    -  'click a.js-toggle-board-choose'(event) {
    -    let target = $(event.target);
    -    if (!target.hasClass('js-toggle-board-choose')) {
    -      target = target.parent();
    -    }
    -    const checkboxId = target.attr('id');
    -    $(`#${checkboxId} .materialCheckBox`).toggleClass('is-checked');
    -    $(`#${checkboxId}`).toggleClass('is-checked');
    -  },
    -  'click button.js-email-invite'(event, tpl) {
    -    const emails = $('#email-to-invite')
    -      .val()
    -      .toLowerCase()
    -      .trim()
    -      .split('\n')
    -      .join(',')
    -      .split(',');
    -    const boardsToInvite = [];
    -    $('.js-toggle-board-choose .materialCheckBox.is-checked').each(function () {
    -      boardsToInvite.push($(this).data('id'));
    -    });
    -    const validEmails = [];
    -    emails.forEach((email) => {
    -      if (email && SimpleSchema.RegEx.Email.test(email.trim())) {
    -        validEmails.push(email.trim());
    -      }
    -    });
    -    if (validEmails.length) {
    -      tpl.loading.set(true);
    -      Meteor.call('sendInvitation', validEmails, boardsToInvite, () => {
    -        // if (!err) {
    -        //   TODO - show more info to user
    -        // }
    -        tpl.loading.set(false);
    -      });
    -    }
    -  },
    -  'click button.js-save'(event, tpl) {
    -    tpl.loading.set(true);
    -    $('li').removeClass('has-error');
    -
    -    try {
    -      const host = checkField('#mail-server-host');
    -      const port = checkField('#mail-server-port');
    -      const username = $('#mail-server-username').val().trim();
    -      const password = $('#mail-server-password').val().trim();
    -      const from = checkField('#mail-server-from');
    -      const tls = $('#mail-server-tls.is-checked').length > 0;
    -      Settings.update(ReactiveCache.getCurrentSetting()._id, {
    -        $set: {
    -          'mailServer.host': host,
    -          'mailServer.port': port,
    -          'mailServer.username': username,
    -          'mailServer.password': password,
    -          'mailServer.enableTLS': tls,
    -          'mailServer.from': from,
    -        },
    -      });
    -    } catch (e) {
    -      return;
    -    } finally {
    -      tpl.loading.set(false);
    -    }
    -  },
    -  'click button.js-send-smtp-test-email'() {
    -    Meteor.call('sendSMTPTestEmail', (err, ret) => {
    -      if (!err && ret) {
    -        const message = `${TAPi18n.__(ret.message)}: ${ret.email}`;
    -        alert(message);
    -      } else {
    -        const reason = err.reason || '';
    -        const message = `${TAPi18n.__(err.error)}\n${reason}`;
    -        alert(message);
    -      }
    -    });
    -  },
    -  'click button.js-save-layout'(event, tpl) {
    -    tpl.loading.set(true);
    -    $('li').removeClass('has-error');
    -
    -    const productName = ($('#product-name').val() || '').trim();
    -    const customLoginLogoImageUrl = (
    -      $('#custom-login-logo-image-url').val() || ''
    -    ).trim();
    -    const customLoginLogoLinkUrl = (
    -      $('#custom-login-logo-link-url').val() || ''
    -    ).trim();
    -    const customHelpLinkUrl = ($('#custom-help-link-url').val() || '').trim();
    -    const textBelowCustomLoginLogo = (
    -      $('#text-below-custom-login-logo').val() || ''
    -    ).trim();
    -    const automaticLinkedUrlSchemes = (
    -      $('#automatic-linked-url-schemes').val() || ''
    -    ).trim();
    -    const customTopLeftCornerLogoImageUrl = (
    -      $('#custom-top-left-corner-logo-image-url').val() || ''
    -    ).trim();
    -    const customTopLeftCornerLogoLinkUrl = (
    -      $('#custom-top-left-corner-logo-link-url').val() || ''
    -    ).trim();
    -    const customTopLeftCornerLogoHeight = (
    -      $('#custom-top-left-corner-logo-height').val() || ''
    -    ).trim();
    -
    -    const oidcBtnText = ($('#oidcBtnTextvalue').val() || '').trim();
    -    const mailDomainName = ($('#mailDomainNamevalue').val() || '').trim();
    -    const legalNotice = ($('#legalNoticevalue').val() || '').trim();
    -    const hideLogoChange = $('input[name=hideLogo]:checked').val() === 'true';
    -    const hideCardCounterListChange =
    -      $('input[name=hideCardCounterList]:checked').val() === 'true';
    -    const hideBoardMemberListChange =
    -      $('input[name=hideBoardMemberList]:checked').val() === 'true';
    -    const displayAuthenticationMethod =
    -      $('input[name=displayAuthenticationMethod]:checked').val() === 'true';
    -    const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val();
    -    const spinnerName = ($('#spinnerName').val() || '').trim();
    -
    -    try {
    -      Settings.update(ReactiveCache.getCurrentSetting()._id, {
    -        $set: {
    -          productName,
    -          hideLogo: hideLogoChange,
    -          hideCardCounterList: hideCardCounterListChange,
    -          hideBoardMemberList: hideBoardMemberListChange,
    -          customLoginLogoImageUrl,
    -          customLoginLogoLinkUrl,
    -          customHelpLinkUrl,
    -          textBelowCustomLoginLogo,
    -          customTopLeftCornerLogoImageUrl,
    -          customTopLeftCornerLogoLinkUrl,
    -          customTopLeftCornerLogoHeight,
    -          displayAuthenticationMethod,
    -          defaultAuthenticationMethod,
    -          automaticLinkedUrlSchemes,
    -          spinnerName,
    -          oidcBtnText,
    -          mailDomainName,
    -          legalNotice,
    -        },
    -      });
    -    } catch (e) {
    -      return;
    -    } finally {
    -      tpl.loading.set(false);
    -    }
    -
    -    document.title = productName;
    -  },
    -  'click a.js-toggle-support'(event, tpl) {
    -    tpl.loading.set(true);
    -    const supportPageEnabled = !$(
    -      '.js-toggle-support .materialCheckBox',
    -    ).hasClass('is-checked');
    -    $('.js-toggle-support .materialCheckBox').toggleClass('is-checked');
    -    $('.support-content').toggleClass('hide');
    -    Settings.update(Settings.findOne()._id, {
    -      $set: { supportPageEnabled },
    -    });
    -    tpl.loading.set(false);
    -  },
    -  'click a.js-toggle-support-public'(event, tpl) {
    -    tpl.loading.set(true);
    -    const supportPagePublic = !$(
    -      '.js-toggle-support-public .materialCheckBox',
    -    ).hasClass('is-checked');
    -    $('.js-toggle-support-public .materialCheckBox').toggleClass('is-checked');
    -    Settings.update(Settings.findOne()._id, {
    -      $set: { supportPagePublic },
    -    });
    -    tpl.loading.set(false);
    -  },
    -  'click button.js-support-save'(event, tpl) {
    -    tpl.loading.set(true);
    -    const supportTitle = ($('#support-title').val() || '').trim();
    -    const supportPageText = ($('#support-page-text').val() || '').trim();
    -    try {
    -      Settings.update(Settings.findOne()._id, {
    -        $set: {
    -          supportTitle,
    -          supportPageText,
    -        },
    -      });
    -    } catch (e) {
    -      return;
    -    } finally {
    -      tpl.loading.set(false);
    -    }
    -  },
    -  'click a.js-toggle-custom-head'(event, tpl) {
    -    tpl.loading.set(true);
    -    const customHeadEnabled = !$(
    -      '.js-toggle-custom-head .materialCheckBox',
    -    ).hasClass('is-checked');
    -    $('.js-toggle-custom-head .materialCheckBox').toggleClass('is-checked');
    -    $('.custom-head-settings').toggleClass('hide');
    -    Settings.update(ReactiveCache.getCurrentSetting()._id, {
    -      $set: { customHeadEnabled },
    -    });
    -    tpl.loading.set(false);
    -  },
    -  'click a.js-toggle-custom-manifest'(event, tpl) {
    -    tpl.loading.set(true);
    -    const customManifestEnabled = !$(
    -      '.js-toggle-custom-manifest .materialCheckBox',
    -    ).hasClass('is-checked');
    -    $('.js-toggle-custom-manifest .materialCheckBox').toggleClass('is-checked');
    -    $('.custom-manifest-settings').toggleClass('hide');
    -    Settings.update(ReactiveCache.getCurrentSetting()._id, {
    -      $set: { customManifestEnabled },
    -    });
    -    tpl.loading.set(false);
    -  },
    -  'click button.js-custom-head-save'(event, tpl) {
    -    tpl.loading.set(true);
    -    const customHeadMetaTags = $('#custom-head-meta').val() || '';
    -    let customManifestContent = $('#custom-manifest-content').val() || '';
    -
    -    // Validate and clean JSON if present
    -    if (customManifestContent.trim()) {
    -      const cleanResult = cleanAndValidateJSON(customManifestContent);
    -      if (cleanResult.error) {
    -        tpl.loading.set(false);
    -        alert(`Invalid manifest JSON: ${cleanResult.error}`);
    -        return;
    -      }
    -      customManifestContent = cleanResult.json;
    -      // Update the textarea with cleaned version
    -      $('#custom-manifest-content').val(customManifestContent);
    -    }
    -
    -    const customHeadLinkTags = $('#custom-head-links').val() || '';
    -
    -    try {
    -      Settings.update(ReactiveCache.getCurrentSetting()._id, {
    -        $set: {
    -          customHeadMetaTags,
    -          customHeadLinkTags,
    -          customManifestContent,
    -        },
    -      });
    -    } catch (e) {
    -      return;
    -    } finally {
    -      tpl.loading.set(false);
    -    }
    -  },
    -  'click a.js-toggle-custom-assetlinks'(event, tpl) {
    -    tpl.loading.set(true);
    -    const customAssetLinksEnabled = !$(
    -      '.js-toggle-custom-assetlinks .materialCheckBox',
    -    ).hasClass('is-checked');
    -    $('.js-toggle-custom-assetlinks .materialCheckBox').toggleClass(
    -      'is-checked',
    -    );
    -    $('.custom-assetlinks-settings').toggleClass('hide');
    -    Settings.update(ReactiveCache.getCurrentSetting()._id, {
    -      $set: { customAssetLinksEnabled },
    -    });
    -    tpl.loading.set(false);
    -  },
    -  'click button.js-custom-assetlinks-save'(event, tpl) {
    -    tpl.loading.set(true);
    -    let customAssetLinksContent = $('#custom-assetlinks-content').val() || '';
    -
    -    // Validate and clean JSON if present
    -    if (customAssetLinksContent.trim()) {
    -      const cleanResult = cleanAndValidateJSON(customAssetLinksContent);
    -      if (cleanResult.error) {
    -        tpl.loading.set(false);
    -        alert(`Invalid assetlinks JSON: ${cleanResult.error}`);
    -        return;
    -      }
    -      customAssetLinksContent = cleanResult.json;
    -      // Update the textarea with cleaned version
    -      $('#custom-assetlinks-content').val(customAssetLinksContent);
    -    }
    -
    -    try {
    -      Settings.update(ReactiveCache.getCurrentSetting()._id, {
    -        $set: {
    -          customAssetLinksContent,
    -        },
    -      });
    -    } catch (e) {
    -      return;
    -    } finally {
    -      tpl.loading.set(false);
    -    }
    +  setLoading(w) {
    +    this.loading.set(w);
       },
     
       // Event handlers for attachment settings
    @@ -796,214 +167,394 @@ Template.setting.events({
       },
     
       // Event handlers for cron settings
    -  // 'click button.js-start-migration'(event, tpl) {
    -  //   event.preventDefault();
    -  //   tpl.loading.set(true);
    -  //   cronIsMigrating.set(true);
    -  //   cronMigrationStatus.set(TAPi18n.__('migration-starting'));
    -  //   cronMigrationCurrentAction.set('');
    -  //   cronMigrationJobProgress.set(0);
    -  //   cronMigrationJobStepNum.set(0);
    -  //   cronMigrationJobTotalSteps.set(0);
    -  //   const selectedIndex = parseInt($('.js-migration-select').val() || '0', 10);
    -  //
    -  //   if (selectedIndex === 0) {
    -  //     // Run all migrations
    -  //     Meteor.call('cron.startAllMigrations', (error, result) => {
    -  //       tpl.loading.set(false);
    -  //       if (error) {
    -  //         alert(TAPi18n.__('migration-start-failed') + ': ' + error.reason);
    -  //       } else {
    -  //         alert(TAPi18n.__('migration-started'));
    -  //       }
    -  //     });
    -  //   } else {
    -  //     // Run specific migration
    -  //     Meteor.call(
    -  //       'cron.startSpecificMigration',
    -  //       selectedIndex - 1,
    -  //       (error, result) => {
    -  //         tpl.loading.set(false);
    -  //         if (error) {
    -  //           alert(TAPi18n.__('migration-start-failed') + ': ' + error.reason);
    -  //         } else if (result && result.skipped) {
    -  //           cronIsMigrating.set(false);
    -  //           cronMigrationStatus.set(TAPi18n.__('migration-not-needed'));
    -  //           alert(TAPi18n.__('migration-not-needed'));
    -  //         } else {
    -  //           alert(TAPi18n.__('migration-started'));
    -  //         }
    -  //       },
    -  //     );
    -  //   }
    -  // },
    -  //
    -  // 'click button.js-start-all-migrations'(event, tpl) {
    -  //   event.preventDefault();
    -  //   tpl.loading.set(true);
    -  //   Meteor.call('cron.startAllMigrations', (error) => {
    -  //     tpl.loading.set(false);
    -  //     if (error) {
    -  //       alert(TAPi18n.__('migration-start-failed') + ': ' + error.reason);
    -  //     } else {
    -  //       alert(TAPi18n.__('migration-started'));
    -  //     }
    -  //   });
    -  // },
    -  //
    -  // 'click button.js-pause-all-migrations'(event, tpl) {
    -  //   event.preventDefault();
    -  //   tpl.loading.set(true);
    -  //   Meteor.call('cron.pauseAllMigrations', (error) => {
    -  //     tpl.loading.set(false);
    -  //     if (error) {
    -  //       alert(TAPi18n.__('migration-pause-failed') + ': ' + error.reason);
    -  //     } else {
    -  //       alert(TAPi18n.__('migration-paused'));
    -  //     }
    -  //   });
    -  // },
    -  //
    -  // 'click button.js-stop-all-migrations'(event, tpl) {
    -  //   event.preventDefault();
    -  //   if (confirm(TAPi18n.__('migration-stop-confirm'))) {
    -  //     tpl.loading.set(true);
    -  //     Meteor.call('cron.stopAllMigrations', (error) => {
    -  //       tpl.loading.set(false);
    -  //       if (error) {
    -  //         alert(TAPi18n.__('migration-stop-failed') + ': ' + error.reason);
    -  //       } else {
    -  //         alert(TAPi18n.__('migration-stopped'));
    -  //       }
    -  //     });
    -  //   }
    -  // },
    -  //
    -  // 'click button.js-pause-migration'(event, tpl) {
    -  //   event.preventDefault();
    -  //   tpl.loading.set(true);
    -  //   cronIsMigrating.set(false);
    -  //   cronMigrationStatus.set(TAPi18n.__('migration-pausing'));
    -  //   Meteor.call('cron.pauseAllMigrations', (error, result) => {
    -  //     tpl.loading.set(false);
    -  //     if (error) {
    -  //       alert(TAPi18n.__('migration-pause-failed') + ': ' + error.reason);
    -  //     } else {
    -  //       alert(TAPi18n.__('migration-paused'));
    -  //     }
    -  //   });
    -  // },
    -  //
    -  // 'click button.js-stop-migration'(event, tpl) {
    -  //   event.preventDefault();
    -  //   if (confirm(TAPi18n.__('migration-stop-confirm'))) {
    -  //     tpl.loading.set(true);
    -  //     cronIsMigrating.set(false);
    -  //     cronMigrationStatus.set(TAPi18n.__('migration-stopping'));
    -  //     cronMigrationCurrentAction.set('');
    -  //     cronMigrationJobProgress.set(0);
    -  //     cronMigrationJobStepNum.set(0);
    -  //     cronMigrationJobTotalSteps.set(0);
    -  //     Meteor.call('cron.stopAllMigrations', (error, result) => {
    -  //       tpl.loading.set(false);
    -  //       if (error) {
    -  //         alert(TAPi18n.__('migration-stop-failed') + ': ' + error.reason);
    -  //       } else {
    -  //         alert(TAPi18n.__('migration-stopped'));
    -  //       }
    -  //     });
    -  //   }
    -  // },
    -  //
    -  // 'click button.js-start-job'(event, tpl) {
    -  //   event.preventDefault();
    -  //   const jobName = $(event.target).data('job-name');
    -  //   tpl.loading.set(true);
    -  //   Meteor.call('cron.startJob', jobName, (error) => {
    -  //     tpl.loading.set(false);
    -  //     if (error) {
    -  //       alert(TAPi18n.__('cron-job-start-failed') + ': ' + error.reason);
    -  //     } else {
    -  //       alert(TAPi18n.__('cron-job-started'));
    -  //     }
    -  //   });
    -  // },
    -  //
    -  // 'click button.js-pause-job'(event, tpl) {
    -  //   event.preventDefault();
    -  //   const jobName = $(event.target).data('job-name');
    -  //   tpl.loading.set(true);
    -  //   Meteor.call('cron.pauseJob', jobName, (error) => {
    -  //     tpl.loading.set(false);
    -  //     if (error) {
    -  //       alert(TAPi18n.__('cron-job-pause-failed') + ': ' + error.reason);
    -  //     } else {
    -  //       alert(TAPi18n.__('cron-job-paused'));
    -  //     }
    -  //   });
    -  // },
    -  //
    -  // 'click button.js-resume-job'(event, tpl) {
    -  //   event.preventDefault();
    -  //   const jobName = $(event.target).data('job-name');
    -  //   tpl.loading.set(true);
    -  //   Meteor.call('cron.resumeJob', jobName, (error) => {
    -  //     tpl.loading.set(false);
    -  //     if (error) {
    -  //       alert(TAPi18n.__('cron-job-resume-failed') + ': ' + error.reason);
    -  //     } else {
    -  //       alert(TAPi18n.__('cron-job-resumed'));
    -  //     }
    -  //   });
    -  // },
    -  //
    -  // 'click button.js-delete-job'(event, tpl) {
    -  //   event.preventDefault();
    -  //   const jobName = $(event.target).data('job-name');
    -  //   if (confirm(TAPi18n.__('cron-job-delete-confirm'))) {
    -  //     tpl.loading.set(true);
    -  //     Meteor.call('cron.removeJob', jobName, (error) => {
    -  //       tpl.loading.set(false);
    -  //       if (error) {
    -  //         alert(TAPi18n.__('cron-job-delete-failed') + ': ' + error.reason);
    -  //       } else {
    -  //         alert(TAPi18n.__('cron-job-deleted'));
    -  //       }
    -  //     });
    -  //   }
    -  // },
    -  //
    -  // 'click button.js-add-cron-job'(event) {
    -  //   event.preventDefault();
    -  //   // Placeholder for adding a new cron job (e.g., open a modal)
    -  //   alert(TAPi18n.__('add-cron-job-placeholder'));
    -  // },
    -});
    -
    -Template.accountSettings.helpers({
    -  allowEmailChange() {
    -    return (
    -      AccountSettings.findOne('accounts-allowEmailChange')?.booleanValue ||
    -      false
    -    );
    +  'click button.js-start-all-migrations'(event) {
    +    event.preventDefault();
    +    Meteor.call('startAllMigrations', (error, result) => {
    +      if (error) {
    +        alert(TAPi18n.__('migration-start-failed') + ': ' + error.reason);
    +      } else {
    +        alert(TAPi18n.__('migration-started'));
    +      }
    +    });
       },
     
    -  allowUserNameChange() {
    -    return (
    -      AccountSettings.findOne('accounts-allowUserNameChange')?.booleanValue ||
    -      false
    -    );
    +  'click button.js-pause-all-migrations'(event) {
    +    event.preventDefault();
    +    Meteor.call('pauseAllMigrations', (error, result) => {
    +      if (error) {
    +        alert(TAPi18n.__('migration-pause-failed') + ': ' + error.reason);
    +      } else {
    +        alert(TAPi18n.__('migration-paused'));
    +      }
    +    });
       },
     
    -  allowUserDelete() {
    -    return (
    -      AccountSettings.findOne('accounts-allowUserDelete')?.booleanValue || false
    -    );
    +  'click button.js-stop-all-migrations'(event) {
    +    event.preventDefault();
    +    if (confirm(TAPi18n.__('migration-stop-confirm'))) {
    +      Meteor.call('stopAllMigrations', (error, result) => {
    +        if (error) {
    +          alert(TAPi18n.__('migration-stop-failed') + ': ' + error.reason);
    +        } else {
    +          alert(TAPi18n.__('migration-stopped'));
    +        }
    +      });
    +    }
       },
    -});
     
    -Template.accountSettings.events({
    -  'click button.js-accounts-save'() {
    +  'click button.js-schedule-board-cleanup'(event) {
    +    event.preventDefault();
    +    Meteor.call('scheduleBoardCleanup', (error, result) => {
    +      if (error) {
    +        alert(TAPi18n.__('board-cleanup-failed') + ': ' + error.reason);
    +      } else {
    +        alert(TAPi18n.__('board-cleanup-scheduled'));
    +      }
    +    });
    +  },
    +
    +  'click button.js-schedule-board-archive'(event) {
    +    event.preventDefault();
    +    Meteor.call('scheduleBoardArchive', (error, result) => {
    +      if (error) {
    +        alert(TAPi18n.__('board-archive-failed') + ': ' + error.reason);
    +      } else {
    +        alert(TAPi18n.__('board-archive-scheduled'));
    +      }
    +    });
    +  },
    +
    +  'click button.js-schedule-board-backup'(event) {
    +    event.preventDefault();
    +    Meteor.call('scheduleBoardBackup', (error, result) => {
    +      if (error) {
    +        alert(TAPi18n.__('board-backup-failed') + ': ' + error.reason);
    +      } else {
    +        alert(TAPi18n.__('board-backup-scheduled'));
    +      }
    +    });
    +  },
    +
    +  'click button.js-pause-job'(event) {
    +    event.preventDefault();
    +    const jobId = $(event.target).data('job-id');
    +    Meteor.call('pauseCronJob', jobId, (error, result) => {
    +      if (error) {
    +        alert(TAPi18n.__('cron-job-pause-failed') + ': ' + error.reason);
    +      } else {
    +        alert(TAPi18n.__('cron-job-paused'));
    +      }
    +    });
    +  },
    +
    +  'click button.js-delete-job'(event) {
    +    event.preventDefault();
    +    const jobId = $(event.target).data('job-id');
    +    if (confirm(TAPi18n.__('cron-job-delete-confirm'))) {
    +      Meteor.call('deleteCronJob', jobId, (error, result) => {
    +        if (error) {
    +          alert(TAPi18n.__('cron-job-delete-failed') + ': ' + error.reason);
    +        } else {
    +          alert(TAPi18n.__('cron-job-deleted'));
    +        }
    +      });
    +    }
    +  },
    +
    +  'click button.js-add-cron-job'(event) {
    +    event.preventDefault();
    +    // Placeholder for adding a new cron job (e.g., open a modal)
    +    alert(TAPi18n.__('add-cron-job-placeholder'));
    +  },
    +
    +  checkField(selector) {
    +    const value = $(selector).val();
    +    if (!value || value.trim() === '') {
    +      $(selector)
    +        .parents('li.smtp-form')
    +        .addClass('has-error');
    +      throw Error('blank field');
    +    } else {
    +      return value;
    +    }
    +  },
    +
    +  boards() {
    +    const ret = ReactiveCache.getBoards(
    +      {
    +        archived: false,
    +        'members.userId': Meteor.userId(),
    +        'members.isAdmin': true,
    +      },
    +      {
    +        sort: { sort: 1 /* boards default sorting */ },
    +      },
    +    );
    +    return ret;
    +  },
    +  toggleForgotPassword() {
    +    this.setLoading(true);
    +    const forgotPasswordClosed = ReactiveCache.getCurrentSetting().disableForgotPassword;
    +    Settings.update(ReactiveCache.getCurrentSetting()._id, {
    +      $set: { disableForgotPassword: !forgotPasswordClosed },
    +    });
    +    this.setLoading(false);
    +  },
    +  toggleRegistration() {
    +    this.setLoading(true);
    +    const registrationClosed = ReactiveCache.getCurrentSetting().disableRegistration;
    +    Settings.update(ReactiveCache.getCurrentSetting()._id, {
    +      $set: { disableRegistration: !registrationClosed },
    +    });
    +    this.setLoading(false);
    +    if (registrationClosed) {
    +      $('.invite-people').slideUp();
    +    } else {
    +      $('.invite-people').slideDown();
    +    }
    +  },
    +  toggleTLS() {
    +    $('#mail-server-tls').toggleClass('is-checked');
    +  },
    +  toggleHideLogo() {
    +    $('#hide-logo').toggleClass('is-checked');
    +  },
    +  toggleHideCardCounterList() {
    +    $('#hide-card-counter-list').toggleClass('is-checked');
    +  },
    +  toggleHideBoardMemberList() {
    +    $('#hide-board-member-list').toggleClass('is-checked');
    +  },
    +  toggleAccessibilityPageEnabled() {
    +    $('#accessibility-page-enabled').toggleClass('is-checked');
    +  },
    +  toggleDisplayAuthenticationMethod() {
    +    $('#display-authentication-method').toggleClass('is-checked');
    +  },
    +
    +  initializeAttachmentSubMenu() {
    +    // Set default sub-menu state for attachment settings
    +    // This will be handled by the attachment settings component
    +    console.log('Initializing attachment sub-menu');
    +  },
    +
    +  initializeCronSubMenu() {
    +    // Set default sub-menu state for cron settings
    +    // This will be handled by the cron settings template
    +    console.log('Initializing cron sub-menu');
    +  },
    +  switchMenu(event) {
    +    const target = $(event.target);
    +    if (!target.hasClass('active')) {
    +      $('.side-menu li.active').removeClass('active');
    +      target.parent().addClass('active');
    +      const targetID = target.data('id');
    +      
    +      // Reset all settings to false
    +      this.forgotPasswordSetting.set(false);
    +      this.generalSetting.set(false);
    +      this.emailSetting.set(false);
    +      this.accountSetting.set(false);
    +      this.tableVisibilityModeSetting.set(false);
    +      this.announcementSetting.set(false);
    +      this.accessibilitySetting.set(false);
    +      this.layoutSetting.set(false);
    +      this.webhookSetting.set(false);
    +      this.attachmentSettings.set(false);
    +      this.cronSettings.set(false);
    +      
    +      // Set the selected setting to true
    +      if (targetID === 'registration-setting') {
    +        this.generalSetting.set(true);
    +      } else if (targetID === 'email-setting') {
    +        this.emailSetting.set(true);
    +      } else if (targetID === 'account-setting') {
    +        this.accountSetting.set(true);
    +      } else if (targetID === 'tableVisibilityMode-setting') {
    +        this.tableVisibilityModeSetting.set(true);
    +      } else if (targetID === 'announcement-setting') {
    +        this.announcementSetting.set(true);
    +      } else if (targetID === 'accessibility-setting') {
    +        this.accessibilitySetting.set(true);
    +      } else if (targetID === 'layout-setting') {
    +        this.layoutSetting.set(true);
    +      } else if (targetID === 'webhook-setting') {
    +        this.webhookSetting.set(true);
    +      } else if (targetID === 'attachment-settings') {
    +        this.attachmentSettings.set(true);
    +        this.initializeAttachmentSubMenu();
    +      } else if (targetID === 'cron-settings') {
    +        this.cronSettings.set(true);
    +        this.initializeCronSubMenu();
    +      }
    +    }
    +  },
    +
    +  checkBoard(event) {
    +    let target = $(event.target);
    +    if (!target.hasClass('js-toggle-board-choose')) {
    +      target = target.parent();
    +    }
    +    const checkboxId = target.attr('id');
    +    $(`#${checkboxId} .materialCheckBox`).toggleClass('is-checked');
    +    $(`#${checkboxId}`).toggleClass('is-checked');
    +  },
    +
    +  inviteThroughEmail() {
    +    const emails = $('#email-to-invite')
    +      .val()
    +      .toLowerCase()
    +      .trim()
    +      .split('\n')
    +      .join(',')
    +      .split(',');
    +    const boardsToInvite = [];
    +    $('.js-toggle-board-choose .materialCheckBox.is-checked').each(function() {
    +      boardsToInvite.push($(this).data('id'));
    +    });
    +    const validEmails = [];
    +    emails.forEach(email => {
    +      if (email && SimpleSchema.RegEx.Email.test(email.trim())) {
    +        validEmails.push(email.trim());
    +      }
    +    });
    +    if (validEmails.length) {
    +      this.setLoading(true);
    +      Meteor.call('sendInvitation', validEmails, boardsToInvite, () => {
    +        // if (!err) {
    +        //   TODO - show more info to user
    +        // }
    +        this.setLoading(false);
    +      });
    +    }
    +  },
    +
    +  saveMailServerInfo() {
    +    this.setLoading(true);
    +    $('li').removeClass('has-error');
    +
    +    try {
    +      const host = this.checkField('#mail-server-host');
    +      const port = this.checkField('#mail-server-port');
    +      const username = $('#mail-server-username')
    +        .val()
    +        .trim();
    +      const password = $('#mail-server-password')
    +        .val()
    +        .trim();
    +      const from = this.checkField('#mail-server-from');
    +      const tls = $('#mail-server-tls.is-checked').length > 0;
    +      Settings.update(ReactiveCache.getCurrentSetting()._id, {
    +        $set: {
    +          'mailServer.host': host,
    +          'mailServer.port': port,
    +          'mailServer.username': username,
    +          'mailServer.password': password,
    +          'mailServer.enableTLS': tls,
    +          'mailServer.from': from,
    +        },
    +      });
    +    } catch (e) {
    +      return;
    +    } finally {
    +      this.setLoading(false);
    +    }
    +  },
    +
    +  saveLayout() {
    +    this.setLoading(true);
    +    $('li').removeClass('has-error');
    +
    +    const productName = ($('#product-name').val() || '').trim();
    +    const customLoginLogoImageUrl = ($('#custom-login-logo-image-url').val() || '').trim();
    +    const customLoginLogoLinkUrl = ($('#custom-login-logo-link-url').val() || '').trim();
    +    const customHelpLinkUrl = ($('#custom-help-link-url').val() || '').trim();
    +    const textBelowCustomLoginLogo = ($('#text-below-custom-login-logo').val() || '').trim();
    +    const automaticLinkedUrlSchemes = ($('#automatic-linked-url-schemes').val() || '').trim();
    +    const customTopLeftCornerLogoImageUrl = ($('#custom-top-left-corner-logo-image-url').val() || '').trim();
    +    const customTopLeftCornerLogoLinkUrl = ($('#custom-top-left-corner-logo-link-url').val() || '').trim();
    +    const customTopLeftCornerLogoHeight = ($('#custom-top-left-corner-logo-height').val() || '').trim();
    +
    +    const oidcBtnText = ($('#oidcBtnTextvalue').val() || '').trim();
    +    const mailDomainName = ($('#mailDomainNamevalue').val() || '').trim();
    +    const legalNotice = ($('#legalNoticevalue').val() || '').trim();
    +    const hideLogoChange = $('input[name=hideLogo]:checked').val() === 'true';
    +    const hideCardCounterListChange = $('input[name=hideCardCounterList]:checked').val() === 'true';
    +    const hideBoardMemberListChange = $('input[name=hideBoardMemberList]:checked').val() === 'true';
    +    const displayAuthenticationMethod =
    +      $('input[name=displayAuthenticationMethod]:checked').val() === 'true';
    +    const defaultAuthenticationMethod = $('#defaultAuthenticationMethod').val();
    +    const spinnerName = ($('#spinnerName').val() || '').trim();
    +
    +    try {
    +      Settings.update(ReactiveCache.getCurrentSetting()._id, {
    +        $set: {
    +          productName,
    +          hideLogo: hideLogoChange,
    +          hideCardCounterList: hideCardCounterListChange,
    +          hideBoardMemberList: hideBoardMemberListChange,
    +          customLoginLogoImageUrl,
    +          customLoginLogoLinkUrl,
    +          customHelpLinkUrl,
    +          textBelowCustomLoginLogo,
    +          customTopLeftCornerLogoImageUrl,
    +          customTopLeftCornerLogoLinkUrl,
    +          customTopLeftCornerLogoHeight,
    +          displayAuthenticationMethod,
    +          defaultAuthenticationMethod,
    +          automaticLinkedUrlSchemes,
    +          spinnerName,
    +          oidcBtnText,
    +          mailDomainName,
    +          legalNotice,
    +        },
    +      });
    +    } catch (e) {
    +      return;
    +    } finally {
    +      this.setLoading(false);
    +    }
    +
    +    DocHead.setTitle(productName);
    +  },
    +
    +  sendSMTPTestEmail() {
    +    Meteor.call('sendSMTPTestEmail', (err, ret) => {
    +      if (!err && ret) {
    +        const message = `${TAPi18n.__(ret.message)}: ${ret.email}`;
    +        alert(message);
    +      } else {
    +        const reason = err.reason || '';
    +        const message = `${TAPi18n.__(err.error)}\n${reason}`;
    +        alert(message);
    +      }
    +    });
    +  },
    +
    +  events() {
    +    return [
    +      {
    +        'click a.js-toggle-forgot-password': this.toggleForgotPassword,
    +        'click a.js-toggle-registration': this.toggleRegistration,
    +        'click a.js-toggle-tls': this.toggleTLS,
    +        'click a.js-setting-menu': this.switchMenu,
    +        'click a.js-toggle-board-choose': this.checkBoard,
    +        'click button.js-email-invite': this.inviteThroughEmail,
    +        'click button.js-save': this.saveMailServerInfo,
    +        'click button.js-send-smtp-test-email': this.sendSMTPTestEmail,
    +        'click a.js-toggle-hide-logo': this.toggleHideLogo,
    +        'click a.js-toggle-hide-card-counter-list': this.toggleHideCardCounterList,
    +        'click a.js-toggle-hide-board-member-list': this.toggleHideBoardMemberList,
    +        'click button.js-save-layout': this.saveLayout,
    +        'click a.js-toggle-display-authentication-method': this
    +          .toggleDisplayAuthenticationMethod,
    +      },
    +    ];
    +  },
    +}).register('setting');
    +
    +BlazeComponent.extendComponent({
    +  saveAccountsChange() {
         const allowEmailChange =
           $('input[name=allowEmailChange]:checked').val() === 'true';
         const allowUserNameChange =
    @@ -1020,7 +571,24 @@ Template.accountSettings.events({
           $set: { booleanValue: allowUserDelete },
         });
       },
    -  'click button.js-all-boards-hide-activities'() {
    +
    +  // Brute force lockout settings method moved to lockedUsersBody.js
    +
    +  allowEmailChange() {
    +    return AccountSettings.findOne('accounts-allowEmailChange')?.booleanValue || false;
    +  },
    +
    +  allowUserNameChange() {
    +    return AccountSettings.findOne('accounts-allowUserNameChange')?.booleanValue || false;
    +  },
    +
    +  allowUserDelete() {
    +    return AccountSettings.findOne('accounts-allowUserDelete')?.booleanValue || false;
    +  },
    +
    +  // Lockout settings helper methods moved to lockedUsersBody.js
    +
    +  allBoardsHideActivities() {
         Meteor.call('setAllBoardsHideActivities', (err, ret) => {
           if (!err && ret) {
             if (ret === true) {
    @@ -1036,25 +604,31 @@ Template.accountSettings.events({
           }
         });
       },
    -});
     
    -Template.tableVisibilityModeSettings.helpers({
    -  allowPrivateOnly() {
    -    return TableVisibilityModeSettings.findOne(
    -      'tableVisibilityMode-allowPrivateOnly',
    -    ).booleanValue;
    +  events() {
    +    return [
    +      {
    +        'click button.js-accounts-save': this.saveAccountsChange,
    +      },
    +      {
    +        'click button.js-all-boards-hide-activities': this.allBoardsHideActivities,
    +      },
    +    ];
       },
    -});
    +}).register('accountSettings');
     
    -Template.tableVisibilityModeSettings.events({
    -  'click button.js-tableVisibilityMode-save'() {
    +BlazeComponent.extendComponent({
    +  saveTableVisibilityChange() {
         const allowPrivateOnly =
           $('input[name=allowPrivateOnly]:checked').val() === 'true';
         TableVisibilityModeSettings.update('tableVisibilityMode-allowPrivateOnly', {
           $set: { booleanValue: allowPrivateOnly },
         });
       },
    -  'click button.js-all-boards-hide-activities'() {
    +  allowPrivateOnly() {
    +    return TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly').booleanValue;
    +  },
    +  allBoardsHideActivities() {
         Meteor.call('setAllBoardsHideActivities', (err, ret) => {
           if (!err && ret) {
             if (ret === true) {
    @@ -1070,88 +644,129 @@ Template.tableVisibilityModeSettings.events({
           }
         });
       },
    -});
     
    -Template.announcementSettings.onCreated(function () {
    -  this.loading = new ReactiveVar(false);
    -});
    +  events() {
    +    return [
    +      {
    +        'click button.js-tableVisibilityMode-save': this.saveTableVisibilityChange,
    +      },
    +      {
    +        'click button.js-all-boards-hide-activities': this.allBoardsHideActivities,
    +      },
    +    ];
    +  },
    +}).register('tableVisibilityModeSettings');
    +
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.loading = new ReactiveVar(false);
    +  },
    +
    +  setLoading(w) {
    +    this.loading.set(w);
    +  },
     
    -Template.announcementSettings.helpers({
       currentAnnouncements() {
         return Announcements.findOne();
       },
    -});
     
    -Template.announcementSettings.events({
    -  'click a.js-toggle-activemessage'(event, tpl) {
    -    tpl.loading.set(true);
    -    const announcements = Announcements.findOne();
    +  saveMessage() {
    +    const message = $('#admin-announcement')
    +      .val()
    +      .trim();
    +    Announcements.update(Announcements.findOne()._id, {
    +      $set: { body: message },
    +    });
    +  },
    +
    +  toggleActive() {
    +    this.setLoading(true);
    +    const announcements = this.currentAnnouncements();
         const isActive = announcements.enabled;
         Announcements.update(announcements._id, {
           $set: { enabled: !isActive },
         });
    -    tpl.loading.set(false);
    +    this.setLoading(false);
         if (isActive) {
           $('.admin-announcement').slideUp();
         } else {
           $('.admin-announcement').slideDown();
         }
       },
    -  'click button.js-announcement-save'() {
    -    const message = $('#admin-announcement').val().trim();
    -    Announcements.update(Announcements.findOne()._id, {
    -      $set: { body: message },
    -    });
    +
    +  events() {
    +    return [
    +      {
    +        'click a.js-toggle-activemessage': this.toggleActive,
    +        'click button.js-announcement-save': this.saveMessage,
    +      },
    +    ];
       },
    -});
    +}).register('announcementSettings');
     
    -Template.accessibilitySettings.onCreated(function () {
    -  this.loading = new ReactiveVar(false);
    -});
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.loading = new ReactiveVar(false);
    +  },
    +
    +  setLoading(w) {
    +    this.loading.set(w);
    +  },
     
    -Template.accessibilitySettings.helpers({
       currentAccessibility() {
         return AccessibilitySettings.findOne();
       },
    -});
    -
    -Template.accessibilitySettings.events({
    -  'click a.js-toggle-accessibility'(event, tpl) {
    -    tpl.loading.set(true);
    -    const accessibilitySetting = AccessibilitySettings.findOne();
    -    const isActive = accessibilitySetting.enabled;
    -    AccessibilitySettings.update(accessibilitySetting._id, {
    -      $set: { enabled: !isActive },
    -    });
    -    tpl.loading.set(false);
    -    if (isActive) {
    -      $('.accessibility-content').slideUp();
    -    } else {
    -      $('.accessibility-content').slideDown();
    -    }
    -  },
    -  'click button.js-accessibility-save'(event, tpl) {
    -    tpl.loading.set(true);
    -    const title = $('#admin-accessibility-title').val().trim();
    -    const content = $('#admin-accessibility-content').val().trim();
     
    +  saveAccessibility() {
    +    this.setLoading(true);
    +    const title = $('#admin-accessibility-title')
    +      .val()
    +      .trim();
    +    const content = $('#admin-accessibility-content')
    +      .val()
    +      .trim();
    +    
         try {
           AccessibilitySettings.update(AccessibilitySettings.findOne()._id, {
             $set: {
               title: title,
    -          body: content,
    +          body: content
             },
           });
         } catch (e) {
           console.error('Error saving accessibility settings:', e);
           return;
         } finally {
    -      tpl.loading.set(false);
    +      this.setLoading(false);
         }
       },
    -});
     
    -Template.selectAuthenticationMethod.onCreated(function () {
    +  toggleAccessibility() {
    +    this.setLoading(true);
    +    const accessibilitySetting = this.currentAccessibility();
    +    const isActive = accessibilitySetting.enabled;
    +    AccessibilitySettings.update(accessibilitySetting._id, {
    +      $set: { enabled: !isActive },
    +    });
    +    this.setLoading(false);
    +    if (isActive) {
    +      $('.accessibility-content').slideUp();
    +    } else {
    +      $('.accessibility-content').slideDown();
    +    }
    +  },
    +
    +  events() {
    +    return [
    +      {
    +        'click a.js-toggle-accessibility': this.toggleAccessibility,
    +        'click button.js-accessibility-save': this.saveAccessibility,
    +      },
    +    ];
    +  },
    +}).register('accessibilitySettings');
    +
    +Template.selectAuthenticationMethod.onCreated(function() {
       this.authenticationMethods = new ReactiveVar([]);
     
       Meteor.call('getAuthenticationsEnabled', (_, result) => {
    @@ -1162,8 +777,8 @@ Template.selectAuthenticationMethod.onCreated(function () {
             { value: 'password' },
             // Gets only the authentication methods availables
             ...Object.entries(result)
    -          .filter((e) => e[1])
    -          .map((e) => ({ value: e[0] })),
    +          .filter(e => e[1])
    +          .map(e => ({ value: e[0] })),
           ]);
         }
       });
    @@ -1186,3 +801,4 @@ Template.selectSpinnerName.helpers({
         return Template.instance().data.spinnerName === match;
       },
     });
    +
    diff --git a/client/components/settings/settingHeader.css b/client/components/settings/settingHeader.css
    index 5b880b9f2..dbd8582e3 100644
    --- a/client/components/settings/settingHeader.css
    +++ b/client/components/settings/settingHeader.css
    @@ -1,5 +1,4 @@
     #header #header-main-bar .setting-header-btn {
    -  border-radius: 3px;
       color: #f2f2f2;
       margin-left: 20px;
       padding-right: 10px;
    diff --git a/client/components/settings/settingHeader.jade b/client/components/settings/settingHeader.jade
    index 8d554ab94..cbbb9b03b 100644
    --- a/client/components/settings/settingHeader.jade
    +++ b/client/components/settings/settingHeader.jade
    @@ -4,39 +4,32 @@ template(name="settingHeaderBar")
     
       .setting-header-btns.left
         if currentUser
    -      a.setting-header-btn.settings(class=isSettingsActive href="{{pathFor 'setting'}}")
    -        span.emoji-icon
    -        i.fa.fa-cog
    +      a.setting-header-btn.settings(href="{{pathFor 'setting'}}")
    +        | ⚙️
             span {{_ 'settings'}}
     
    -      a.setting-header-btn.people(class=isPeopleActive href="{{pathFor 'people'}}")
    -        span.emoji-icon
    -        i.fa.fa-users
    +      a.setting-header-btn.people(href="{{pathFor 'people'}}")
    +        | 👥
             span {{_ 'people'}}
     
    -      a.setting-header-btn.informations(class=isAdminReportsActive href="{{pathFor 'admin-reports'}}")
    -        span.emoji-icon
    -        i.fa.fa-file-text-o
    +      a.setting-header-btn.informations(href="{{pathFor 'admin-reports'}}")
    +        | 📋
             span {{_ 'reports'}}
     
    -      a.setting-header-btn.informations(class=isAttachmentsActive href="{{pathFor 'attachments'}}")
    -        span.emoji-icon
    -        i.fa.fa-paperclip
    +      a.setting-header-btn.informations(href="{{pathFor 'attachments'}}")
    +        | 📎
             span {{_ 'attachments'}}
     
    -      a.setting-header-btn.informations(class=isTranslationActive href="{{pathFor 'translation'}}")
    -        span.emoji-icon
    -        i.fa.fa-globe
    +      a.setting-header-btn.informations(href="{{pathFor 'translation'}}")
    +        | 🔤
             span {{_ 'translation'}}
     
    -      a.setting-header-btn.informations(class=isInformationActive href="{{pathFor 'information'}}")
    -        span.emoji-icon
    -        i.fa.fa-info-circle
    +      a.setting-header-btn.informations(href="{{pathFor 'information'}}")
    +        | ℹ️
             span {{_ 'info'}}
     
         else
           a.setting-header-btn.js-log-in(
             title="{{_ 'log-in'}}")
    -        span.emoji-icon
    -        i.fa.fa-sign-in
    +        | 🚪
             span {{_ 'log-in'}}
    diff --git a/client/components/settings/settingHeader.js b/client/components/settings/settingHeader.js
    deleted file mode 100644
    index b52eb8f49..000000000
    --- a/client/components/settings/settingHeader.js
    +++ /dev/null
    @@ -1,22 +0,0 @@
    -import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
    -
    -Template.settingHeaderBar.helpers({
    -  isSettingsActive() {
    -    return FlowRouter.getRouteName() === 'setting' ? 'active' : '';
    -  },
    -  isPeopleActive() {
    -    return FlowRouter.getRouteName() === 'people' ? 'active' : '';
    -  },
    -  isAdminReportsActive() {
    -    return FlowRouter.getRouteName() === 'admin-reports' ? 'active' : '';
    -  },
    -  isAttachmentsActive() {
    -    return FlowRouter.getRouteName() === 'attachments' ? 'active' : '';
    -  },
    -  isTranslationActive() {
    -    return FlowRouter.getRouteName() === 'translation' ? 'active' : '';
    -  },
    -  isInformationActive() {
    -    return FlowRouter.getRouteName() === 'information' ? 'active' : '';
    -  },
    -});
    diff --git a/client/components/settings/translationBody.jade b/client/components/settings/translationBody.jade
    index 5c19533e2..deb721a22 100644
    --- a/client/components/settings/translationBody.jade
    +++ b/client/components/settings/translationBody.jade
    @@ -9,12 +9,12 @@ template(name="translation")
                 +spinner
               else if translationSetting.get
                 span
    -              i.fa.fa-globe
    +              | 🔤
                   unless isMiniScreen
                     | {{_ 'translation'}}
                 input#searchTranslationInput(placeholder="{{_ 'search'}}")
                 button#searchTranslationButton
    -              i.fa.fa-search
    +              | 🔍
                   | {{_ 'search'}}
                 .ext-box-right
                   span {{#unless isMiniScreen}}{{_ 'translation-number'}}{{/unless}} #{translationNumber}
    @@ -24,7 +24,7 @@ template(name="translation")
               ul
                 li.active
                   a.js-translation-menu(data-id="translation-setting")
    -                i.fa.fa-globe
    +                | 🔤
                     | {{_ 'translation'}}
             .main-body
               if loading.get
    @@ -47,7 +47,7 @@ template(name="translationGeneral")
     
     template(name="newTranslationRow")
       a.new-translation
    -    i.fa.fa-plus
    +    | ➕
         | {{_ 'new'}}
     
     template(name="translationRow")
    @@ -57,7 +57,7 @@ template(name="translationRow")
         td {{translationData.translationText}}
         td
           a.edit-translation
    -        i.fa.fa-pencil-square-o
    +        | ✏️
             | {{_ 'edit'}}
           a.more-settings-translation
             | ⋯
    diff --git a/client/components/settings/translationBody.js b/client/components/settings/translationBody.js
    index 76de7beff..c9a5c71ad 100644
    --- a/client/components/settings/translationBody.js
    +++ b/client/components/settings/translationBody.js
    @@ -1,118 +1,111 @@
     import { ReactiveCache } from '/imports/reactiveCache';
    -import { InfiniteScrolling } from '/client/lib/infiniteScrolling';
     
     const translationsPerPage = 25;
     
    -Template.translation.onCreated(function () {
    -  this.error = new ReactiveVar('');
    -  this.loading = new ReactiveVar(false);
    -  this.translationSetting = new ReactiveVar(true);
    -  this.findTranslationsOptions = new ReactiveVar({});
    -  this.numberTranslations = new ReactiveVar(0);
    +BlazeComponent.extendComponent({
    +  mixins() {
    +    return [Mixins.InfiniteScrolling];
    +  },
    +  onCreated() {
    +    this.error = new ReactiveVar('');
    +    this.loading = new ReactiveVar(false);
    +    this.translationSetting = new ReactiveVar(true);
    +    this.findTranslationsOptions = new ReactiveVar({});
    +    this.numberTranslations = new ReactiveVar(0);
     
    -  this.page = new ReactiveVar(1);
    -  this.loadNextPageLocked = false;
    -  this.infiniteScrolling = new InfiniteScrolling();
    +    this.page = new ReactiveVar(1);
    +    this.loadNextPageLocked = false;
    +    this.callFirstWith(null, 'resetNextPeak');
    +    this.autorun(() => {
    +      const limitTranslations = this.page.get() * translationsPerPage;
     
    -  this.loadNextPage = () => {
    +      this.subscribe('translation', this.findTranslationsOptions.get(), 0, () => {
    +        this.loadNextPageLocked = false;
    +        const nextPeakBefore = this.callFirstWith(null, 'getNextPeak');
    +        this.calculateNextPeak();
    +        const nextPeakAfter = this.callFirstWith(null, 'getNextPeak');
    +        if (nextPeakBefore === nextPeakAfter) {
    +          this.callFirstWith(null, 'resetNextPeak');
    +        }
    +      });
    +    });
    +  },
    +  events() {
    +    return [
    +      {
    +        'click #searchTranslationButton'() {
    +          this.filterTranslation();
    +        },
    +        'keydown #searchTranslationInput'(event) {
    +          if (event.keyCode === 13 && !event.shiftKey) {
    +            this.filterTranslation();
    +          }
    +        },
    +        'click #newTranslationButton'() {
    +          Popup.open('newTranslation');
    +        },
    +        'click a.js-translation-menu': this.switchMenu,
    +      },
    +    ];
    +  },
    +  filterTranslation() {
    +    const value = $('#searchTranslationInput').first().val();
    +    if (value === '') {
    +      this.findTranslationsOptions.set({});
    +    } else {
    +      const regex = new RegExp(value, 'i');
    +      this.findTranslationsOptions.set({
    +        $or: [
    +          { language: regex },
    +          { text: regex },
    +          { translationText: regex },
    +        ],
    +      });
    +    }
    +  },
    +  loadNextPage() {
         if (this.loadNextPageLocked === false) {
           this.page.set(this.page.get() + 1);
           this.loadNextPageLocked = true;
         }
    -  };
    -
    -  this.calculateNextPeak = () => {
    +  },
    +  calculateNextPeak() {
         const element = this.find('.main-body');
         if (element) {
    -      this.infiniteScrolling.setNextPeak(element.scrollHeight);
    +      const altitude = element.scrollHeight;
    +      this.callFirstWith(this, 'setNextPeak', altitude);
         }
    -  };
    -
    -  this.autorun(() => {
    -    const limitTranslations = this.page.get() * translationsPerPage;
    -
    -    this.subscribe('translation', this.findTranslationsOptions.get(), 0, () => {
    -      this.loadNextPageLocked = false;
    -      const nextPeakBefore = this.infiniteScrolling.getNextPeak();
    -      this.calculateNextPeak();
    -      const nextPeakAfter = this.infiniteScrolling.getNextPeak();
    -      if (nextPeakBefore === nextPeakAfter) {
    -        this.infiniteScrolling.resetNextPeak();
    -      }
    -    });
    -  });
    -});
    -
    -Template.translation.helpers({
    -  loading() {
    -    return Template.instance().loading;
       },
    -  translationSetting() {
    -    return Template.instance().translationSetting;
    +  reachNextPeak() {
    +    this.loadNextPage();
    +  },
    +  setError(error) {
    +    this.error.set(error);
    +  },
    +  setLoading(w) {
    +    this.loading.set(w);
       },
       translationList() {
    -    const tpl = Template.instance();
    -    const translations = ReactiveCache.getTranslations(tpl.findTranslationsOptions.get(), {
    +    const translations = ReactiveCache.getTranslations(this.findTranslationsOptions.get(), {
           sort: { modifiedAt: 1 },
           fields: { _id: true },
         });
    -    tpl.numberTranslations.set(translations.length);
    +    this.numberTranslations.set(translations.length);
         return translations;
       },
       translationNumber() {
    -    return Template.instance().numberTranslations.get();
    +    return this.numberTranslations.get();
       },
    -  setError(error) {
    -    Template.instance().error.set(error);
    -  },
    -  setLoading(w) {
    -    Template.instance().loading.set(w);
    -  },
    -});
    -
    -Template.translation.events({
    -  'click #searchTranslationButton'(event, tpl) {
    -    _filterTranslation(tpl);
    -  },
    -  'keydown #searchTranslationInput'(event, tpl) {
    -    if (event.keyCode === 13 && !event.shiftKey) {
    -      _filterTranslation(tpl);
    -    }
    -  },
    -  'click #newTranslationButton'() {
    -    Popup.open('newTranslation');
    -  },
    -  'click a.js-translation-menu'(event, tpl) {
    +  switchMenu(event) {
         const target = $(event.target);
         if (!target.hasClass('active')) {
           $('.side-menu li.active').removeClass('active');
           target.parent().addClass('active');
           const targetID = target.data('id');
    -      tpl.translationSetting.set('translation-setting' === targetID);
    +      this.translationSetting.set('translation-setting' === targetID);
         }
       },
    -  'scroll .main-body'(event, tpl) {
    -    tpl.infiniteScrolling.checkScrollPosition(event.currentTarget, () => {
    -      tpl.loadNextPage();
    -    });
    -  },
    -});
    -
    -function _filterTranslation(tpl) {
    -  const value = $('#searchTranslationInput').first().val();
    -  if (value === '') {
    -    tpl.findTranslationsOptions.set({});
    -  } else {
    -    const regex = new RegExp(value, 'i');
    -    tpl.findTranslationsOptions.set({
    -      $or: [
    -        { language: regex },
    -        { text: regex },
    -        { translationText: regex },
    -      ],
    -    });
    -  }
    -}
    +}).register('translation');
     
     Template.translationRow.helpers({
       translationData() {
    @@ -142,20 +135,30 @@ Template.newTranslationPopup.helpers({
       },
     });
     
    -Template.translationRow.helpers({
    +BlazeComponent.extendComponent({
    +  onCreated() {},
       translation() {
         return ReactiveCache.getTranslation(this.translationId);
       },
    -});
    +  events() {
    +    return [
    +      {
    +        'click a.edit-translation': Popup.open('editTranslation'),
    +        'click a.more-settings-translation': Popup.open('settingsTranslation'),
    +      },
    +    ];
    +  },
    +}).register('translationRow');
     
    -Template.translationRow.events({
    -  'click a.edit-translation': Popup.open('editTranslation'),
    -  'click a.more-settings-translation': Popup.open('settingsTranslation'),
    -});
    -
    -Template.newTranslationRow.events({
    -  'click a.new-translation': Popup.open('newTranslation'),
    -});
    +BlazeComponent.extendComponent({
    +  events() {
    +    return [
    +      {
    +        'click a.new-translation': Popup.open('newTranslation'),
    +      },
    +    ];
    +  },
    +}).register('newTranslationRow');
     
     Template.editTranslationPopup.events({
       submit(event, templateInstance) {
    @@ -205,7 +208,7 @@ Template.newTranslationPopup.events({
     Template.settingsTranslationPopup.events({
       'click #deleteButton'(event) {
         event.preventDefault();
    -    Meteor.call('deleteTranslation', this.translationId);
    +    Translation.remove(this.translationId);
         Popup.back();
       }
     });
    diff --git a/client/components/sidebar/sidebar.css b/client/components/sidebar/sidebar.css
    index 5b0ad44cf..7867aec6d 100644
    --- a/client/components/sidebar/sidebar.css
    +++ b/client/components/sidebar/sidebar.css
    @@ -68,14 +68,6 @@
       transform-origin: 100% 100% !important;
     }
     
    -/* Grey checkmarks when grey icons setting is enabled */
    -body.grey-icons-enabled .sidebar .materialCheckBox.is-checked,
    -body.grey-icons-enabled .boardCardSettingsPopup .materialCheckBox.is-checked,
    -body.grey-icons-enabled .boardSubtaskSettingsPopup .materialCheckBox.is-checked {
    -  border-bottom: 2px solid #7a7a7a !important;
    -  border-right: 2px solid #7a7a7a !important;
    -}
    -
     /* Card Settings 3-column grid layout */
     .card-settings-grid {
       display: grid;
    @@ -138,11 +130,6 @@ body.grey-icons-enabled .boardSubtaskSettingsPopup .materialCheckBox.is-checked
     }
     .sidebar .sidebar-content ul.sidebar-list li > a .fa.fa-check {
       margin: 0 4px;
    -  color: #3cb500;
    -}
    -/* Grey check icons when grey icons setting is enabled */
    -body.grey-icons-enabled .sidebar .sidebar-content ul.sidebar-list li > a .fa.fa-check {
    -  color: #7a7a7a;
     }
     .sidebar .sidebar-content ul.sidebar-list li .minicard {
       padding: 6px 8px 4px;
    @@ -162,9 +149,6 @@ body.grey-icons-enabled .sidebar .sidebar-content ul.sidebar-list li > a .fa.fa-
       border-radius: 3px;
       background: #e6e6e6;
     }
    -.sidebar .sidebar-content .sidebar-btn * {
    -  color: #fff;
    -}
     .sidebar .sidebar-content .sidebar-btn:hover * {
       color: #fff;
     }
    diff --git a/client/components/sidebar/sidebar.jade b/client/components/sidebar/sidebar.jade
    index 8b8d81ea1..662c84ad8 100644
    --- a/client/components/sidebar/sidebar.jade
    +++ b/client/components/sidebar/sidebar.jade
    @@ -1,32 +1,28 @@
     template(name="sidebar")
       .board-sidebar.sidebar(class="{{#if isOpen}}is-open{{/if}} {{#unless isVerticalScrollbars}}no-scrollbars{{/unless}}")
         //a.sidebar-tongue.js-toggle-sidebar(
    -    //
    -      class="{{#if isTongueHidden}}is-hidden{{/if}}",
    -    //
    -      title="{{showTongueTitle}}")
    -    //
    -      i.fa.fa-navicon
    +    //  class="{{#if isTongueHidden}}is-hidden{{/if}}",
    +    //  title="{{showTongueTitle}}")
    +    //  i.fa.fa-navicon
         .sidebar-actions
           .sidebar-shortcuts
             a.sidebar-btn.js-shortcuts(title="{{_ 'keyboard-shortcuts' }}")
    -          i.fa.fa-keyboard-o
    +          | ⌨️
               span {{_ 'keyboard-shortcuts' }}
             a.sidebar-btn.js-keyboard-shortcuts-toggle(
               title="{{#if isKeyboardShortcuts}}{{_ 'keyboard-shortcuts-enabled'}}{{else}}{{_ 'keyboard-shortcuts-disabled'}}{{/if}}")
    -          i.fa(class="{{#if isKeyboardShortcuts}}fa-check{{else}}fa-ban{{/if}}")
    +          | {{#if isKeyboardShortcuts}}✅{{else}}🚫{{/if}}
           if isAccessibilityEnabled
             a.sidebar-accessibility
    -          i.fa.fa-universal-access
    +          | ♿
               span {{_ 'accessibility'}}
           a.sidebar-xmark.js-close-sidebar ✕
         .sidebar-content.js-board-sidebar-content
           //a.hide-btn.js-hide-sidebar
    -      //
    -        i.fa.fa-navicon
    +      //  i.fa.fa-navicon
           unless isDefaultView
             h2
    -          a.fa.fa-arrow-left.js-back-home
    +          a.js-back-home | ⬅️
               = getViewTitle
           if isOpen
             +Template.dynamic(template=getViewTemplate)
    @@ -38,25 +34,25 @@ template(name='homeSidebar')
       hr
       ul#cards.label-text-hidden
         a.flex.js-toggle-minicard-label-text(title="{{_ 'hide-minicard-label-text'}}")
    -      i.fa(class="{{#if hiddenMinicardLabelText}}fa-check{{else}}fa-square-o{{/if}}")
    +      span {{#if hiddenMinicardLabelText}}✅{{else}}⬜{{/if}}
           span {{_ 'hide-minicard-label-text'}}
       if currentUser
         ul#cards.vertical-scrollbars-toggle
           a.flex.js-vertical-scrollbars-toggle(title="{{_ 'enable-vertical-scrollbars'}}")
    -        i.fa(class="{{#if isVerticalScrollbars}}fa-check{{else}}fa-square-o{{/if}}")
    +        span {{#if isVerticalScrollbars}}✅{{else}}⬜{{/if}}
             span {{_ 'enable-vertical-scrollbars'}}
       ul#cards.show-week-of-year-toggle
         a.flex.js-show-week-of-year-toggle(title="{{_ 'show-week-of-year'}}")
    -      i.fa(class="{{#if isShowWeekOfYear}}fa-check{{else}}fa-square-o{{/if}}")
    +      span {{#if isShowWeekOfYear}}✅{{else}}⬜{{/if}}
           span {{_ 'show-week-of-year'}}
       hr
    -  if currentUser.isBoardAdmin
    +  unless currentUser.isNoComments
         h3.activity-title
    -      i.fa.fa-comment-o
    +      | 💬
           | {{_ 'activities'}}
     
           a.flex.js-toggle-show-activities(title="{{_ 'show-activities'}}")
    -        i.fa(class="{{#if showActivities}}fa-check{{else}}fa-square-o{{/if}}")
    +        span {{#if showActivities}}✅{{else}}⬜{{/if}}
             span {{_ 'show-activities'}}
         +activities(mode="board")
     
    @@ -65,11 +61,11 @@ template(name="membersWidget")
         unless currentUser.isWorker
           h3
             a.board-header-btn.js-open-board-menu(title="{{_ 'boardMenuPopup-title'}}")
    -          i.fa.fa-cog
    +          | ⚙️
               | {{_ 'boardMenuPopup-title'}}
       hr
       h3
    -    i.fa.fa-users
    +    | 👥
         | {{_ 'members'}}
       +basicTabs(tabs=tabs)
           +tabContent(slug="people")
    @@ -81,15 +77,15 @@ template(name="membersWidget")
               if isSandstorm
                 if currentUser.isBoardMember
                   a.member.add-member.sandstorm-powerbox-request-identity(title="{{_ 'add-members'}}")
    -                i.fa.fa-plus
    +                | ➕
               else if currentUser.isBoardAdmin
                 a.member.add-member.js-manage-board-members(title="{{_ 'add-members'}}")
    -              i.fa.fa-plus
    +              | ➕
               .clearfix
               if isInvited
                 hr
                 p
    -              i.fa.fa-exclamation-triangle
    +              | ⚠️
                   | {{_ 'just-invited'}}
                 button.js-member-invite-accept.primary {{_ 'accept'}}
                 button.js-member-invite-decline {{_ 'decline'}}
    @@ -125,7 +121,7 @@ template(name="boardOrgGeneral")
             th
               if currentUser.isBoardAdmin
                 a.member.orgOrTeamMember.add-member.js-manage-board-addOrg(title="{{_ 'add-members'}}")
    -              i.fa.fa-plus
    +              | ➕
                 .divaddfaplusminus
                   | {{_ 'add'}}
           each org in currentBoard.activeOrgs
    @@ -146,7 +142,7 @@ template(name="boardTeamGeneral")
             th
               if currentUser.isBoardAdmin
                 a.member.orgOrTeamMember.add-member.js-manage-board-addTeam(title="{{_ 'add-members'}}")
    -              i.fa.fa-plus
    +              | ➕
                 .divaddfaplusminus
                   | {{_ 'add'}}
           each currentBoard.activeTeams
    @@ -159,7 +155,7 @@ template(name="boardChangeColorPopup")
             span.background-box(class="board-color-{{this}}")
               span {{this}}
               if isSelected
    -            i.fa.fa-check
    +            | ✅
     
     template(name="boardChangeBackgroundImagePopup")
       form
    @@ -183,16 +179,16 @@ template(name="boardInfoOnMyBoardsPopup")
         unless currentSetting.hideCardCounterList
           div.check-div
             a.flex.js-field-has-cardcounterlist(class="{{#if allowsCardCounterList}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsCardCounterList}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsCardCounterList}}✅{{else}}⬜{{/if}}
               span
    -            i.fa.fa-sign-in
    +            | 🚪
                 | {{_ 'show-card-counter-per-list'}}
         unless currentSetting.hideBoardMemberList
           div.check-div
             a.flex.js-field-has-boardmemberlist(class="{{#if allowsBoardMemberList}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsBoardMemberList}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsBoardMemberList}}✅{{else}}⬜{{/if}}
               span
    -            i.fa.fa-hourglass
    +            | ⏳
                 | {{_ 'show-board_members-avatar'}}
     
     template(name="boardCardSettingsPopup")
    @@ -207,170 +203,169 @@ template(name="boardCardSettingsPopup")
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-receiveddate(title="{{_ 'card-received'}}" class="{{#if allowsReceivedDate}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsReceivedDate}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsReceivedDate}}✅{{else}}⬜{{/if}}
           .card-settings-column
             a.flex.js-field-has-receiveddate(title="{{_ 'card-received'}}" class="{{#if allowsReceivedDate}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsReceivedDate}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsReceivedDate}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-sign-in
    +          | 🚪
               | {{_ 'card-received'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-startdate(title="{{_ 'card-start'}}" class="{{#if allowsStartDate}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsStartDate}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsStartDate}}✅{{else}}⬜{{/if}}
           .card-settings-column
             a.flex.js-field-has-startdate(title="{{_ 'card-start'}}" class="{{#if allowsStartDate}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsStartDate}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsStartDate}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-hourglass
    +          | ⏳
               | {{_ 'card-start'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-duedate(title="{{_ 'card-due'}}" class="{{#if allowsDueDate}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsDueDate}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsDueDate}}✅{{else}}⬜{{/if}}
           .card-settings-column
             a.flex.js-field-has-duedate(title="{{_ 'card-due'}}" class="{{#if allowsDueDate}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsDueDate}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsDueDate}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-sign-in
    +          | 🚪
               | {{_ 'card-due'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-enddate(title="{{_ 'card-end'}}" class="{{#if allowsEndDate}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsEndDate}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsEndDate}}✅{{else}}⬜{{/if}}
           .card-settings-column
             a.flex.js-field-has-enddate(title="{{_ 'card-end'}}" class="{{#if allowsEndDate}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsEndDate}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsEndDate}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-clock-o
    +          | ⏰
               | {{_ 'card-end'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-members(title="{{_ 'members'}}" class="{{#if allowsMembers}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsMembers}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsMembers}}✅{{else}}⬜{{/if}}
           .card-settings-column
             a.flex.js-field-has-members(title="{{_ 'members'}}" class="{{#if allowsMembers}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsMembers}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsMembers}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-users
    +          | 👥
               | {{_ 'members'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-creator(title="{{_ 'creator'}}" class="{{#if allowsCreator}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsCreator}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsCreator}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
           .card-settings-column
             span
    -          i.fa.fa-user
    +          | 👤
               | {{_ 'creator'}}
         .card-settings-row
           .card-settings-column
             span
           .card-settings-column
             a.flex.js-field-has-creator-on-minicard(title="{{_ 'creator-on-minicard'}}" class="{{#if allowsCreatorOnMinicard}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsCreatorOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsCreatorOnMinicard}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-user
    +          | 👤
               | {{_ 'creator-on-minicard'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-assignee(title="{{_ 'assignee'}}" class="{{#if allowsAssignee}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsAssignee}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsAssignee}}✅{{else}}⬜{{/if}}
           .card-settings-column
             a.flex.js-field-has-assignee(title="{{_ 'assignee'}}" class="{{#if allowsAssignee}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsAssignee}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsAssignee}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-user
    +          | 👤
               | {{_ 'assignee'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-assigned-by(title="{{_ 'assigned-by'}}" class="{{#if allowsAssignedBy}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsAssignedBy}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsAssignedBy}}✅{{else}}⬜{{/if}}
           .card-settings-column
             a.flex.js-field-has-assigned-by(title="{{_ 'assigned-by'}}" class="{{#if allowsAssignedBy}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsAssignedBy}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsAssignedBy}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-shopping-cart
    +          | 🛒
               | {{_ 'assigned-by'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-requested-by(title="{{_ 'requested-by'}}" class="{{#if allowsRequestedBy}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsRequestedBy}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsRequestedBy}}✅{{else}}⬜{{/if}}
           .card-settings-column
             a.flex.js-field-has-requested-by(title="{{_ 'requested-by'}}" class="{{#if allowsRequestedBy}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsRequestedBy}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsRequestedBy}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-user
    -          | ➕
    +          | 👤➕
               | {{_ 'requested-by'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-card-sorting-by-number(title="{{_ 'card-sorting-by-number'}}" class="{{#if allowsCardSortingByNumber}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsCardSortingByNumber}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsCardSortingByNumber}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
           .card-settings-column
             span
    -          i.fa.fa-sort-numeric-asc
    +          | 🔢
               | {{_ 'card-sorting-by-number'}}
         .card-settings-row
           .card-settings-column
             span
           .card-settings-column
             a.flex.js-field-has-card-sorting-by-number-on-minicard(title="{{_ 'card-sorting-by-number-on-minicard'}}" class="{{#if allowsCardSortingByNumberOnMinicard}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsCardSortingByNumberOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsCardSortingByNumberOnMinicard}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-sort-numeric-asc
    +          | 🔢
               | {{_ 'card-sorting-by-number-on-minicard'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-card-show-lists(title="{{_ 'card-show-lists'}}" class="{{#if allowsShowLists}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsShowLists}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsShowLists}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
           .card-settings-column
             span
    -          i.fa.fa-list
    +          | 📋
               | {{_ 'card-show-lists'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-labels(title="{{_ 'labels'}}" class="{{#if allowsLabels}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsLabels}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsLabels}}✅{{else}}⬜{{/if}}
           .card-settings-column
             a.flex.js-field-has-labels(title="{{_ 'labels'}}" class="{{#if allowsLabels}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsLabels}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsLabels}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-tag
    +          | 🏷️
               | {{_ 'labels'}}
         .card-settings-row
           .card-settings-column
             span
           .card-settings-column
             a.flex.js-field-has-card-show-lists-on-minicard(title="{{_ 'card-show-lists-on-minicard'}}" class="{{#if allowsShowListsOnMinicard}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsShowListsOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsShowListsOnMinicard}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-list
    +          | 📋
               | {{_ 'card-show-lists-on-minicard'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-card-number(title="{{_ 'card'}} {{_ 'number'}}" class="{{#if allowsCardNumber}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsCardNumber}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsCardNumber}}✅{{else}}⬜{{/if}}
           .card-settings-column
             a.flex.js-field-has-card-number(title="{{_ 'card'}} {{_ 'number'}}" class="{{#if allowsCardNumber}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsCardNumber}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsCardNumber}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
               | #️⃣
    @@ -379,25 +374,25 @@ template(name="boardCardSettingsPopup")
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-description-title(title="{{_ 'description'}} {{_ 'title'}}" class="{{#if allowsDescriptionTitle}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsDescriptionTitle}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsDescriptionTitle}}✅{{else}}⬜{{/if}}
           .card-settings-column
             a.flex.js-field-has-description-title(title="{{_ 'description'}} {{_ 'title'}}" class="{{#if allowsDescriptionTitle}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsDescriptionTitle}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsDescriptionTitle}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-file-text-o
    +          | 📝
               | {{_ 'description'}}
               | {{_ 'title'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-description-text(title="{{_ 'description'}} {{_ 'custom-field-text'}}" class="{{#if allowsDescriptionText}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsDescriptionText}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsDescriptionText}}✅{{else}}⬜{{/if}}
           .card-settings-column
             a.flex.js-field-has-description-text(title="{{_ 'description'}} {{_ 'custom-field-text'}}" class="{{#if allowsDescriptionText}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsDescriptionText}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsDescriptionText}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-file-text-o
    +          | 📝
               | {{_ 'description'}}
               | {{_ 'custom-field-text'}}
         .card-settings-row
    @@ -405,111 +400,102 @@ template(name="boardCardSettingsPopup")
             span
           .card-settings-column
             a.flex.js-field-has-description-text-on-minicard(title="{{_ 'description-on-minicard'}}" class="{{#if allowsDescriptionTextOnMinicard}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsDescriptionTextOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsDescriptionTextOnMinicard}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-file-text-o
    +          | 📝
               | {{_ 'description-on-minicard'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-checklists(title="{{_ 'checklists'}}" class="{{#if allowsChecklists}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsChecklists}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsChecklists}}✅{{else}}⬜{{/if}}
           .card-settings-column
             a.flex.js-field-has-checklists(title="{{_ 'checklists'}}" class="{{#if allowsChecklists}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsChecklists}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsChecklists}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-check
    +          | ✅
               | {{_ 'checklists'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-subtasks(title="{{_ 'subtasks'}}" class="{{#if allowsSubtasks}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsSubtasks}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsSubtasks}}✅{{else}}⬜{{/if}}
           .card-settings-column
             a.flex.js-field-has-subtasks(title="{{_ 'subtasks'}}" class="{{#if allowsSubtasks}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsSubtasks}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsSubtasks}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-globe
    +          | 🌐
               | {{_ 'subtasks'}}
         .card-settings-row
           .card-settings-column
             a.flex.js-field-has-attachments(title="{{_ 'attachments'}}" class="{{#if allowsAttachments}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsAttachments}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsAttachments}}✅{{else}}⬜{{/if}}
           .card-settings-column
             a.flex.js-field-has-attachments(title="{{_ 'attachments'}}" class="{{#if allowsAttachments}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsAttachments}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsAttachments}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-paperclip
    +          | 📎
               | {{_ 'attachments'}}
         .card-settings-row
           .card-settings-column
             span
           .card-settings-column
             a.flex.js-field-has-badge-attachment-on-minicard(title="{{_ 'badge-attachment-on-minicard'}}" class="{{#if allowsBadgeAttachmentOnMinicard}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsBadgeAttachmentOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsBadgeAttachmentOnMinicard}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-paperclip
    +          | 📎
               | {{_ 'badge-attachment-on-minicard'}}
         .card-settings-row
           .card-settings-column
             span
           .card-settings-column
             a.flex.js-field-has-cover-attachment-on-minicard(title="{{_ 'cover-attachment-on-minicard'}}" class="{{#if allowsCoverAttachmentOnMinicard}}is-checked{{/if}}")
    -          i.fa(class="{{#if allowsCoverAttachmentOnMinicard}}fa-check{{else}}fa-square-o{{/if}}")
    +          span {{#if allowsCoverAttachmentOnMinicard}}✅{{else}}⬜{{/if}}
           .card-settings-column
             span
    -          i.fa.fa-picture-o
    +          | 📖
    +          | 🖼️
               | {{_ 'cover-attachment-on-minicard'}}
         //div.check-div
    -    //
    -      a.flex.js-field-has-comments(class="{{#if allowsComments}}is-checked{{/if}}")
    -    //
    -        .materialCheckBox(class="{{#if allowsComments}}is-checked{{/if}}")
    -    //
    -        span
    -    //
    -          i.fa.fa-comment-o
    -    //
    -          | {{_ 'comment'}}
    +    //  a.flex.js-field-has-comments(class="{{#if allowsComments}}is-checked{{/if}}")
    +    //    .materialCheckBox(class="{{#if allowsComments}}is-checked{{/if}}")
    +    //    span
    +    //      i.fa.fa-comment-o
    +    //      | {{_ 'comment'}}
         //div.check-div
    -    //
    -      a.flex.js-field-has-activities(class="{{#if allowsActivities}}is-checked{{/if}}")
    -    //
    -        .materialCheckBox(class="{{#if allowsActivities}}is-checked{{/if}}")
    -    //
    -        span
    -    //
    -          i.fa.fa-history
    -    //
    -          | {{_ 'activities'}}
    +    //  a.flex.js-field-has-activities(class="{{#if allowsActivities}}is-checked{{/if}}")
    +    //    .materialCheckBox(class="{{#if allowsActivities}}is-checked{{/if}}")
    +    //    span
    +    //      i.fa.fa-history
    +    //      | {{_ 'activities'}}
     
     template(name="boardSubtaskSettingsPopup")
       form.board-subtask-settings
         h3 {{_ 'show-parent-in-minicard'}}
           a#prefix-with-full-path.flex.js-field-show-parent-in-minicard(title="{{_ 'prefix-with-full-path'}}" class="{{#if $eq presentParentTask 'prefix-with-full-path'}}is-checked{{/if}}")
    -        i.fa(class="{{#if $eq presentParentTask 'prefix-with-full-path'}}fa-check{{else}}fa-square-o{{/if}}")
    +        span {{#if $eq presentParentTask 'prefix-with-full-path'}}✅{{else}}⬜{{/if}}
             span {{_ 'prefix-with-full-path'}}
           a#prefix-with-parent.flex.js-field-show-parent-in-minicard(title="{{_ 'prefix-with-parent'}}" class="{{#if $eq presentParentTask 'prefix-with-parent'}}is-checked{{/if}}")
    -        i.fa(class="{{#if $eq presentParentTask 'prefix-with-parent'}}fa-check{{else}}fa-square-o{{/if}}")
    +        span {{#if $eq presentParentTask 'prefix-with-parent'}}✅{{else}}⬜{{/if}}
             span {{_ 'prefix-with-parent'}}
           a#subtext-with-full-path.flex.js-field-show-parent-in-minicard(title="{{_ 'subtext-with-full-path'}}" class="{{#if $eq presentParentTask 'subtext-with-full-path'}}is-checked{{/if}}")
    -        i.fa(class="{{#if $eq presentParentTask 'subtext-with-full-path'}}fa-check{{else}}fa-square-o{{/if}}")
    +        span {{#if $eq presentParentTask 'subtext-with-full-path'}}✅{{else}}⬜{{/if}}
             span {{_ 'subtext-with-full-path'}}
           a#subtext-with-parent.flex.js-field-show-parent-in-minicard(title="{{_ 'subtext-with-parent'}}" class="{{#if $eq presentParentTask 'subtext-with-parent'}}is-checked{{/if}}")
    -        i.fa(class="{{#if $eq presentParentTask 'subtext-with-parent'}}fa-check{{else}}fa-square-o{{/if}}")
    +        span {{#if $eq presentParentTask 'subtext-with-parent'}}✅{{else}}⬜{{/if}}
             span {{_ 'subtext-with-parent'}}
           a#no-parent.flex.js-field-show-parent-in-minicard(title="{{_ 'no-parent'}}" class="{{#if $eq presentParentTask 'no-parent'}}is-checked{{/if}}")
    -        i.fa(class="{{#if $eq presentParentTask 'no-parent'}}fa-check{{else}}fa-square-o{{/if}}")
    +        span {{#if $eq presentParentTask 'no-parent'}}✅{{else}}⬜{{/if}}
             span {{_ 'no-parent'}}
         div
           hr
     
         div.check-div
           a.flex.js-field-has-subtasks(title="{{_ 'show-subtasks-field'}}" class="{{#if allowsSubtasks}}is-checked{{/if}}")
    -        i.fa(class="{{#if allowsSubtasks}}fa-check{{else}}fa-square-o{{/if}}")
    +        span {{#if allowsSubtasks}}✅{{else}}⬜{{/if}}
             span {{_ 'show-subtasks-field'}}
     
         label
    @@ -548,20 +534,20 @@ template(name="chooseBoardSource")
     template(name="archiveBoardPopup")
       p {{_ 'close-board-pop'}}
       button.js-confirm.negate.full(type="submit")
    -    i.fa.fa-archive
    +    | 📦
         | {{_ 'archive'}}
     
     template(name="deleteDuplicateListsPopup")
       p {{_ 'delete-duplicate-lists-confirm'}}
       button.js-confirm.negate.full(type="submit")
    -    i.fa.fa-trash
    +    | 🗑️
         | {{_ 'delete'}}
     
     template(name="outgoingWebhooksPopup")
       each integrations
         form.integration-form
           a.flex
    -        i.fa(class="{{#unless enabled}}fa-check{{else}}fa-square-o{{/unless}}")
    +        span {{#unless enabled}}✅{{else}}⬜{{/unless}}
             span {{_ 'disable-webhook'}}
           input.js-outgoing-webhooks-title(placeholder="{{_ 'webhook-title'}}" type="text" name="title" value=title)
           input.js-outgoing-webhooks-url(type="text" name="url" value=url)
    @@ -589,107 +575,98 @@ template(name="boardMenuPopup")
         if currentUser.isBoardAdmin
           li
             a.js-open-rules-view(title="{{_ 'rules'}}")
    -          i.fa.fa-magic
    +          | ✨
               | {{_ 'rules'}}
         if currentUser.isBoardAdmin
           li
             a.js-custom-fields
    -          i.fa.fa-file-text-o
    +          | 📝
               | {{_ 'custom-fields'}}
         li
           a.js-open-archives
    -        i.fa.fa-archive
    +        | 📦
             | {{_ 'archived-items'}}
         if currentUser.isBoardAdmin
           li
             a.js-change-board-color
    -          i.fa.fa-paint-brush
    +          | 🎨
               | {{_ 'board-change-color'}}
           li
             a.js-change-background-image
    -          i.fa.fa-picture-o
    +          | 🖼️
               | {{_ 'board-change-background-image'}}
         //Bug Board icons random dance https://github.com/wekan/wekan/issues/4214
         //if currentUser.isBoardAdmin
    -    //
    -      unless currentSetting.hideBoardMemberList
    -    //
    -        unless currentSetting.hideCardCounterList
    -    //
    -          li
    -    //
    -            a.js-board-info-on-my-boards(title="{{_ 'board-info-on-my-boards'}}")
    -    //
    -              i.fa.fa-id-card-o
    -    //
    -              | {{_ 'board-info-on-my-boards'}}
    +    //  unless currentSetting.hideBoardMemberList
    +    //    unless currentSetting.hideCardCounterList
    +    //      li
    +    //        a.js-board-info-on-my-boards(title="{{_ 'board-info-on-my-boards'}}")
    +    //          i.fa.fa-id-card-o
    +    //          | {{_ 'board-info-on-my-boards'}}
       hr
       ul.pop-over-list
         if withApi
           li
             a.js-export-board
    -          i.fa.fa-upload
    +          | 📤
               | {{_ 'export-board'}}
         if currentUser.isBoardAdmin
           li
             a.js-outgoing-webhooks
    -          i.fa.fa-globe
    +          | 🌐
               | {{_ 'outgoing-webhooks'}}
           li
             a.js-card-settings
    -          i.fa.fa-id-card
    +          | 🃏
               | {{_ 'card-settings'}}
           li
             a.js-subtask-settings
    -          i.fa.fa-globe
    +          | 🌐
               | {{_ 'subtask-settings'}}
       unless currentBoard.isTemplatesBoard
         if currentUser.isBoardAdmin
           hr
           ul.pop-over-list
    -        // li
    -        //
    -           a.js-delete-duplicate-lists
    -        //
    -             | 🗑️
    -        //
    -             | {{_ 'delete-duplicate-lists'}}
    +        li
    +          a.js-delete-duplicate-lists
    +            | 🗑️
    +            | {{_ 'delete-duplicate-lists'}}
             li
               a.js-archive-board
    -            i.fa.fa-archive
    +            | ➡️📦
                 | {{_ 'archive-board'}}
     
    -template(name="exportBoardPopup")
    +template(name="exportBoard")
       ul.pop-over-list
         li
           a.download-json-link(href="{{exportUrl}}", download="{{exportJsonFilename}}")
    -        i.fa.fa-upload
    +        | 📤
             | {{_ 'export-board-json'}}
         li
           a(href="{{exportUrlExcel}}", download="{{exportFilenameExcel}}")
    -        i.fa.fa-upload
    +        | 📤
             | {{_ 'export-board-excel'}}
         li
           a(href="{{exportCsvUrl}}", download="{{exportCsvFilename}}")
    -        i.fa.fa-upload
    +        | 📤
             | {{_ 'export-board-csv'}} ,
         li
           a(href="{{exportScsvUrl}}", download="{{exportCsvFilename}}")
    -        i.fa.fa-upload
    +        | 📤
             | {{_ 'export-board-csv'}} ;
         li
           a(href="{{exportTsvUrl}}", download="{{exportTsvFilename}}")
    -        i.fa.fa-upload
    +        | 📤
             | {{_ 'export-board-tsv'}}
         li
           a.html-export-board
    -        i.fa.fa-archive
    +        | 📦
             | {{_ 'export-board-html'}}
     
     template(name="labelsWidget")
       .board-widget.board-widget-labels
         h3
    -      i.fa.fa-tag
    +      | 🏷️
           | {{_ 'labels'}}
         .board-widget-content
           each currentBoard.labels
    @@ -700,7 +677,7 @@ template(name="labelsWidget")
                     = name
           if currentUser.isBoardAdmin
             a.card-label.add-label.js-add-label(title="{{_ 'label-create'}}")
    -          i.fa.fa-plus
    +          | ➕
     
     template(name="memberPopup")
       .board-member-menu
    @@ -712,7 +689,7 @@ template(name="memberPopup")
             p.quiet @#{user.username}
             if isInvited
               p
    -            i.fa.fa-exclamation-triangle
    +            | ⚠️
                 | {{_ 'not-accepted-yet'}}
     
         ul.pop-over-list
    @@ -775,6 +752,7 @@ template(name="removeBoardTeamPopup")
     template(name="addMemberPopup")
       .js-search-member
         input.js-search-member-input(type="text" placeholder="{{_ 'email-address'}}")
    +
       if loading.get
         +spinner
       else if error.get
    @@ -782,12 +760,21 @@ template(name="addMemberPopup")
       else
         ul.pop-over-list
           each searchResults
    -        li.item.js-member-item
    +        li.item.js-member-item(class="{{#if isBoardMember}}disabled{{/if}}")
               a.name.js-select-member(title="{{profile.fullname}} ({{username}})")
                 +userAvatar(userId=_id)
                 span.full-name
                   = profile.fullname
                   | ({{username}})
    +            if isBoardMember
    +              .quiet ({{_ 'joined'}})
    +
    +      if searching.get
    +        +spinner
    +
    +      if noResults.get
    +        .manage-member-section
    +          p.quiet {{_ 'no-results'}}
         button.js-email-invite.primary.full {{_ 'email-invite'}}
     
     
    @@ -810,56 +797,32 @@ template(name="changePermissionsPopup")
           a(class="{{#if isLastAdmin}}disabled{{else}}js-set-admin{{/if}}")
             | {{_ 'admin'}}
             if isAdmin
    -          i.fa.fa-check
    +          | ✅
             span.sub-name {{_ 'admin-desc'}}
         li
           a(class="{{#if isLastAdmin}}disabled{{else}}js-set-normal{{/if}}")
             | {{_ 'normal'}}
             if isNormal
    -          i.fa.fa-check
    +          | ✅
             span.sub-name {{_ 'normal-desc'}}
    -    li
    -      a(class="{{#if isLastAdmin}}disabled{{else}}js-set-normal-assigned-only{{/if}}")
    -        | {{_ 'normal-assigned-only'}}
    -        if isNormalAssignedOnly
    -          i.fa.fa-check
    -        span.sub-name {{_ 'normal-assigned-only-desc'}}
         li
           a(class="{{#if isLastAdmin}}disabled{{else}}js-set-no-comments{{/if}}")
             | {{_ 'no-comments'}}
             if isNoComments
    -          i.fa.fa-check
    +          | ✅
             span.sub-name {{_ 'no-comments-desc'}}
         li
           a(class="{{#if isLastAdmin}}disabled{{else}}js-set-comment-only{{/if}}")
             | {{_ 'comment-only'}}
             if isCommentOnly
    -          i.fa.fa-check
    +          | ✅
             span.sub-name {{_ 'comment-only-desc'}}
    -    li
    -      a(class="{{#if isLastAdmin}}disabled{{else}}js-set-comment-assigned-only{{/if}}")
    -        | {{_ 'comment-assigned-only'}}
    -        if isCommentAssignedOnly
    -          i.fa.fa-check
    -        span.sub-name {{_ 'comment-assigned-only-desc'}}
         li
           a(class="{{#if isLastAdmin}}disabled{{else}}js-set-worker{{/if}}")
             | {{_ 'worker'}}
             if isWorker
    -          i.fa.fa-check
    +          | ✅
             span.sub-name {{_ 'worker-desc'}}
    -    li
    -      a(class="{{#if isLastAdmin}}disabled{{else}}js-set-read-only{{/if}}")
    -        | {{_ 'read-only'}}
    -        if isReadOnly
    -          i.fa.fa-check
    -        span.sub-name {{_ 'read-only-desc'}}
    -    li
    -      a(class="{{#if isLastAdmin}}disabled{{else}}js-set-read-assigned-only{{/if}}")
    -        | {{_ 'read-assigned-only'}}
    -        if isReadAssignedOnly
    -          i.fa.fa-check
    -        span.sub-name {{_ 'read-assigned-only-desc'}}
       if isLastAdmin
         hr
         p.quiet.bottom {{_ 'last-admin-desc'}}
    diff --git a/client/components/sidebar/sidebar.js b/client/components/sidebar/sidebar.js
    index 648a244f8..18f271691 100644
    --- a/client/components/sidebar/sidebar.js
    +++ b/client/components/sidebar/sidebar.js
    @@ -1,7 +1,5 @@
     import { ReactiveCache } from '/imports/reactiveCache';
     import { TAPi18n } from '/imports/i18n';
    -import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
    -import { InfiniteScrolling } from '/client/lib/infiniteScrolling';
     
     Sidebar = null;
     
    @@ -17,67 +15,75 @@ const viewTitles = {
       archives: 'archives',
     };
     
    -Template.sidebar.onCreated(function() {
    -  this._isOpen = new ReactiveVar(false);
    -  this._view = new ReactiveVar(defaultView);
    -  this._hideCardCounterList = new ReactiveVar(false);
    -  this._hideBoardMemberList = new ReactiveVar(false);
    -  this.infiniteScrolling = new InfiniteScrolling();
    -  this.activitiesInstance = null;
    -  Sidebar = this;
    +BlazeComponent.extendComponent({
    +  mixins() {
    +    return [Mixins.InfiniteScrolling];
    +  },
     
    -  // Subscribe to accessibility settings
    -  Meteor.subscribe('accessibilitySettings');
    +  onCreated() {
    +    this._isOpen = new ReactiveVar(false);
    +    this._view = new ReactiveVar(defaultView);
    +    this._hideCardCounterList = new ReactiveVar(false);
    +    this._hideBoardMemberList = new ReactiveVar(false);
    +    Sidebar = this;
     
    -  // Methods on the template instance for programmatic access via the Sidebar global
    -  this.isOpen = function() {
    +    // Subscribe to accessibility settings
    +    Meteor.subscribe('accessibilitySettings');
    +  },
    +
    +  onDestroyed() {
    +    Sidebar = null;
    +  },
    +
    +  isOpen() {
         return this._isOpen.get();
    -  };
    +  },
     
    -  this.open = function() {
    +  open() {
         if (!this._isOpen.get()) {
           this._isOpen.set(true);
           EscapeActions.executeUpTo('detailsPane');
         }
    -  };
    +  },
     
    -  this.hide = function() {
    +  hide() {
         if (this._isOpen.get()) {
           this._isOpen.set(false);
         }
    -  };
    +  },
     
    -  this.toggle = function() {
    +  toggle() {
         this._isOpen.set(!this._isOpen.get());
    -  };
    +  },
     
    -  this.calculateNextPeak = function() {
    +  calculateNextPeak() {
         const sidebarElement = this.find('.js-board-sidebar-content');
         if (sidebarElement) {
           const altitude = sidebarElement.scrollHeight;
    -      this.infiniteScrolling.setNextPeak(altitude);
    +      this.callFirstWith(this, 'setNextPeak', altitude);
         }
    -  };
    +  },
     
    -  this.reachNextPeak = function() {
    -    if (this.activitiesInstance && typeof this.activitiesInstance.loadNextPage === 'function') {
    -      this.activitiesInstance.loadNextPage();
    +  reachNextPeak() {
    +    const activitiesChildren = this.childComponents('activities');
    +    if (activitiesChildren && activitiesChildren.length > 0 && activitiesChildren[0] && typeof activitiesChildren[0].loadNextPage === 'function') {
    +      activitiesChildren[0].loadNextPage();
         }
    -  };
    +  },
     
    -  this.isTongueHidden = function() {
    +  isTongueHidden() {
         return this.isOpen() && this.getView() !== defaultView;
    -  };
    +  },
     
    -  this.scrollTop = function() {
    +  scrollTop() {
         this.$('.js-board-sidebar-content').scrollTop(0);
    -  };
    +  },
     
    -  this.getView = function() {
    +  getView() {
         return this._view.get();
    -  };
    +  },
     
    -  this.setView = function(view) {
    +  setView(view) {
         view = _.isString(view) ? view : defaultView;
         if (this._view.get() !== view) {
           this._view.set(view);
    @@ -85,114 +91,85 @@ Template.sidebar.onCreated(function() {
           EscapeActions.executeUpTo('detailsPane');
         }
         this.open();
    -  };
    +  },
     
    -  this.isDefaultView = function() {
    +  isDefaultView() {
         return this.getView() === defaultView;
    -  };
    +  },
     
    -  this.getViewTemplate = function() {
    +  getViewTemplate() {
         return `${this.getView()}Sidebar`;
    -  };
    +  },
     
    -  this.getViewTitle = function() {
    +  getViewTitle() {
         return TAPi18n.__(viewTitles[this.getView()]);
    -  };
    +  },
     
    -  this.showTongueTitle = function() {
    +  showTongueTitle() {
         if (this.isOpen()) return `${TAPi18n.__('sidebar-close')}`;
         else return `${TAPi18n.__('sidebar-open')}`;
    -  };
    -});
    +  },
     
    -Template.sidebar.onDestroyed(function() {
    -  Sidebar = null;
    -});
    -
    -Template.sidebar.helpers({
    -  isOpen() {
    -    return Sidebar && Sidebar.isOpen();
    -  },
    -  isTongueHidden() {
    -    return Sidebar && Sidebar.isTongueHidden();
    -  },
    -  isDefaultView() {
    -    return Sidebar && Sidebar.isDefaultView();
    -  },
    -  getViewTemplate() {
    -    return Sidebar && Sidebar.getViewTemplate();
    -  },
    -  getViewTitle() {
    -    return Sidebar && Sidebar.getViewTitle();
    -  },
    -  showTongueTitle() {
    -    return Sidebar && Sidebar.showTongueTitle();
    -  },
       isKeyboardShortcuts() {
         const user = ReactiveCache.getCurrentUser();
         return user && user.isKeyboardShortcuts();
       },
    +
       isVerticalScrollbars() {
         const user = ReactiveCache.getCurrentUser();
         return user && user.isVerticalScrollbars();
       },
    +
       isAccessibilityEnabled() {
         const setting = AccessibilitySettings.findOne({});
         return setting && setting.enabled;
       },
    -});
     
    -Template.sidebar.events({
    -  'click .js-hide-sidebar'(event, tpl) {
    -    tpl.hide();
    +  events() {
    +    return [
    +      {
    +        'click .js-hide-sidebar': this.hide,
    +        'click .js-toggle-sidebar': this.toggle,
    +        'click .js-back-home': this.setView,
    +        'click .js-toggle-minicard-label-text'() {
    +          currentUser = ReactiveCache.getCurrentUser();
    +          if (currentUser) {
    +            Meteor.call('toggleMinicardLabelText');
    +          } else if (window.localStorage.getItem('hiddenMinicardLabelText')) {
    +            window.localStorage.removeItem('hiddenMinicardLabelText');
    +            location.reload();
    +          } else {
    +            window.localStorage.setItem('hiddenMinicardLabelText', 'true');
    +            location.reload();
    +          }
    +        },
    +        'click .js-shortcuts'() {
    +          FlowRouter.go('shortcuts');
    +        },
    +        'click .js-keyboard-shortcuts-toggle'() {
    +          ReactiveCache.getCurrentUser().toggleKeyboardShortcuts();
    +        },
    +        'click .js-vertical-scrollbars-toggle'() {
    +          ReactiveCache.getCurrentUser().toggleVerticalScrollbars();
    +        },
    +        'click .js-show-week-of-year-toggle'() {
    +          ReactiveCache.getCurrentUser().toggleShowWeekOfYear();
    +        },
    +        'click .sidebar-accessibility'() {
    +          FlowRouter.go('accessibility');
    +          Sidebar.toggle();
    +        },
    +        'click .js-close-sidebar'() {
    +          Sidebar.toggle()
    +        },
    +      },
    +    ];
       },
    -  'click .js-toggle-sidebar'(event, tpl) {
    -    tpl.toggle();
    -  },
    -  'click .js-back-home'(event, tpl) {
    -    tpl.setView();
    -  },
    -  'click .js-toggle-minicard-label-text'() {
    -    currentUser = ReactiveCache.getCurrentUser();
    -    if (currentUser) {
    -      Meteor.call('toggleMinicardLabelText');
    -    } else if (window.localStorage.getItem('hiddenMinicardLabelText')) {
    -      window.localStorage.removeItem('hiddenMinicardLabelText');
    -      location.reload();
    -    } else {
    -      window.localStorage.setItem('hiddenMinicardLabelText', 'true');
    -      location.reload();
    -    }
    -  },
    -  'click .js-shortcuts'() {
    -    FlowRouter.go('shortcuts');
    -  },
    -  'click .js-keyboard-shortcuts-toggle'() {
    -    ReactiveCache.getCurrentUser().toggleKeyboardShortcuts();
    -  },
    -  'click .js-vertical-scrollbars-toggle'() {
    -    ReactiveCache.getCurrentUser().toggleVerticalScrollbars();
    -  },
    -  'click .js-show-week-of-year-toggle'() {
    -    ReactiveCache.getCurrentUser().toggleShowWeekOfYear();
    -  },
    -  'click .sidebar-accessibility'() {
    -    FlowRouter.go('accessibility');
    -    Sidebar.toggle();
    -  },
    -  'click .js-close-sidebar'() {
    -    Sidebar.toggle()
    -  },
    -  'scroll .js-board-sidebar-content'(event, tpl) {
    -    tpl.infiniteScrolling.checkScrollPosition(event.currentTarget, () => {
    -      tpl.reachNextPeak();
    -    });
    -  },
    -});
    +}).register('sidebar');
     
     Blaze.registerHelper('Sidebar', () => Sidebar);
     
    -Template.homeSidebar.helpers({
    +BlazeComponent.extendComponent({
       hiddenMinicardLabelText() {
         currentUser = ReactiveCache.getCurrentUser();
         if (currentUser) {
    @@ -215,13 +192,16 @@ Template.homeSidebar.helpers({
         let ret = Utils.getCurrentBoard().showActivities ?? false;
         return ret;
       },
    -});
    -
    -Template.homeSidebar.events({
    -  'click .js-toggle-show-activities'() {
    -    Utils.getCurrentBoard().toggleShowActivities();
    +  events() {
    +    return [
    +      {
    +        'click .js-toggle-show-activities'() {
    +          Utils.getCurrentBoard().toggleShowActivities();
    +        },
    +      },
    +    ];
       },
    -});
    +}).register('homeSidebar');
     
     
     
    @@ -258,20 +238,8 @@ Template.memberPopup.helpers({
           const commentOnly = currentBoard.hasCommentOnly(this.userId);
           const noComments = currentBoard.hasNoComments(this.userId);
           const worker = currentBoard.hasWorker(this.userId);
    -      const normalAssignedOnly = currentBoard.hasNormalAssignedOnly(this.userId);
    -      const commentAssignedOnly = currentBoard.hasCommentAssignedOnly(this.userId);
    -      const readOnly = currentBoard.hasReadOnly(this.userId);
    -      const readAssignedOnly = currentBoard.hasReadAssignedOnly(this.userId);
    -      if (readAssignedOnly) {
    -        return TAPi18n.__('read-assigned-only');
    -      } else if (readOnly) {
    -        return TAPi18n.__('read-only');
    -      } else if (commentAssignedOnly) {
    -        return TAPi18n.__('comment-assigned-only');
    -      } else if (commentOnly) {
    +      if (commentOnly) {
             return TAPi18n.__('comment-only');
    -      } else if (normalAssignedOnly) {
    -        return TAPi18n.__('normal-assigned-only');
           } else if (noComments) {
             return TAPi18n.__('no-comments');
           } else if (worker) {
    @@ -310,10 +278,10 @@ Template.boardMenuPopup.events({
       'click .js-delete-duplicate-lists': Popup.afterConfirm('deleteDuplicateLists', function() {
         const currentBoard = Utils.getCurrentBoard();
         if (!currentBoard) return;
    -
    +    
         // Get all lists in the current board
         const allLists = ReactiveCache.getLists({ boardId: currentBoard._id, archived: false });
    -
    +    
         // Group lists by title to find duplicates
         const listsByTitle = {};
         allLists.forEach(list => {
    @@ -322,7 +290,7 @@ Template.boardMenuPopup.events({
           }
           listsByTitle[list.title].push(list);
         });
    -
    +    
         // Find and delete duplicate lists that have no cards
         let deletedCount = 0;
         Object.keys(listsByTitle).forEach(title => {
    @@ -332,7 +300,7 @@ Template.boardMenuPopup.events({
             for (let i = 1; i < listsWithSameTitle.length; i++) {
               const list = listsWithSameTitle[i];
               const cardsInList = ReactiveCache.getCards({ listId: list._id, archived: false });
    -
    +          
               if (cardsInList.length === 0) {
                 Lists.remove(list._id);
                 deletedCount++;
    @@ -340,15 +308,15 @@ Template.boardMenuPopup.events({
             }
           }
         });
    -
    +    
         // Show notification
         if (deletedCount > 0) {
           // You could add a toast notification here if available
         }
       }),
    -  'click .js-archive-board ': Popup.afterConfirm('archiveBoard', async function() {
    +  'click .js-archive-board ': Popup.afterConfirm('archiveBoard', function() {
         const currentBoard = Utils.getCurrentBoard();
    -    await currentBoard.archive();
    +    currentBoard.archive();
         // XXX We should have some kind of notification on top of the page to
         // confirm that the board was successfully archived.
         FlowRouter.go('home');
    @@ -401,7 +369,7 @@ Template.memberPopup.events({
         Popup.back();
       },
       'click .js-change-role': Popup.open('changePermissions'),
    -  'click .js-remove-member': Popup.afterConfirm('removeMember', async function() {
    +  'click .js-remove-member': Popup.afterConfirm('removeMember', function() {
         // This works from removing member from board, card members and assignees.
         const boardId = Session.get('currentBoard');
         const memberId = this.userId;
    @@ -411,7 +379,7 @@ Template.memberPopup.events({
         ReactiveCache.getCards({ boardId, assignees: memberId }).forEach(card => {
           card.unassignAssignee(memberId);
         });
    -    await ReactiveCache.getBoard(boardId).removeMember(memberId);
    +    ReactiveCache.getBoard(boardId).removeMember(memberId);
         Popup.back();
       }),
       'click .js-leave-member': Popup.afterConfirm('leaveBoard', () => {
    @@ -421,7 +389,6 @@ Template.memberPopup.events({
           FlowRouter.go('home');
         });
       }),
    -
     });
     
     Template.removeMemberPopup.helpers({
    @@ -438,42 +405,50 @@ Template.leaveBoardPopup.helpers({
         return Utils.getCurrentBoard();
       },
     });
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.error = new ReactiveVar('');
    +    this.loading = new ReactiveVar(false);
    +    this.findOrgsOptions = new ReactiveVar({});
    +    this.findTeamsOptions = new ReactiveVar({});
     
    -Template.membersWidget.onCreated(function() {
    -  this.error = new ReactiveVar('');
    -  this.loading = new ReactiveVar(false);
    -  this.findOrgsOptions = new ReactiveVar({});
    -  this.findTeamsOptions = new ReactiveVar({});
    +    this.page = new ReactiveVar(1);
    +    this.teamPage = new ReactiveVar(1);
    +    this.autorun(() => {
    +      const limitOrgs = this.page.get() * Number.MAX_SAFE_INTEGER;
    +      this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => {});
    +    });
     
    -  this.page = new ReactiveVar(1);
    -  this.teamPage = new ReactiveVar(1);
    -  this.autorun(() => {
    -    const limitOrgs = this.page.get() * Number.MAX_SAFE_INTEGER;
    -    this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => {});
    -  });
    +    this.autorun(() => {
    +      const limitTeams = this.teamPage.get() * Number.MAX_SAFE_INTEGER;
    +      this.subscribe('team', this.findTeamsOptions.get(), limitTeams, () => {});
    +    });
    +  },
     
    -  this.autorun(() => {
    -    const limitTeams = this.teamPage.get() * Number.MAX_SAFE_INTEGER;
    -    this.subscribe('team', this.findTeamsOptions.get(), limitTeams, () => {});
    -  });
    +  onRendered() {
    +    this.setLoading(false);
    +  },
     
    -  this.setError = function(error) {
    +  setError(error) {
         this.error.set(error);
    -  };
    +  },
     
    -  this.setLoading = function(w) {
    +  setLoading(w) {
         this.loading.set(w);
    -  };
    +  },
     
    -  this.isLoading = function() {
    +  isLoading() {
         return this.loading.get();
    -  };
    -});
    +  },
     
    -Template.membersWidget.onRendered(function() {
    -  const tpl = Template.instance();
    -  if (tpl.setLoading) tpl.setLoading(false);
    -});
    +  tabs() {
    +    return [
    +      { name: TAPi18n.__('people'), slug: 'people' },
    +      { name: TAPi18n.__('organizations'), slug: 'organizations' },
    +      { name: TAPi18n.__('teams'), slug: 'teams' },
    +    ];
    +  },
    +}).register('membersWidget');
     
     Template.membersWidget.helpers({
       isInvited() {
    @@ -506,13 +481,6 @@ Template.membersWidget.helpers({
     
         return teams.length > 0;
       },
    -  tabs() {
    -    return [
    -      { name: TAPi18n.__('people'), slug: 'people' },
    -      { name: TAPi18n.__('organizations'), slug: 'organizations' },
    -      { name: TAPi18n.__('teams'), slug: 'teams' },
    -    ];
    -  },
     });
     
     Template.membersWidget.events({
    @@ -522,6 +490,7 @@ Template.membersWidget.events({
       'click .js-manage-board-addOrg': Popup.open('addBoardOrg'),
       'click .js-manage-board-addTeam': Popup.open('addBoardTeam'),
       'click .js-import': Popup.open('boardImportBoard'),
    +  submit: this.onSubmit,
       'click .js-import-board': Popup.open('chooseBoardSource'),
       'click .js-open-archived-board'() {
         Modal.open('archivedBoards');
    @@ -544,16 +513,12 @@ Template.membersWidget.events({
       },
     });
     
    -Template.outgoingWebhooksPopup.onCreated(function() {
    -  this.disabled = new ReactiveVar(false);
    -});
    -
    -Template.outgoingWebhooksPopup.helpers({
    +BlazeComponent.extendComponent({
       boardId() {
         return Session.get('currentBoard') || Integrations.Const.GLOBAL_WEBHOOK_ID;
       },
       integrations() {
    -    const boardId = Session.get('currentBoard') || Integrations.Const.GLOBAL_WEBHOOK_ID;
    +    const boardId = this.boardId();
         const ret = ReactiveCache.getIntegrations({ boardId });
         return ret;
       },
    @@ -561,74 +526,80 @@ Template.outgoingWebhooksPopup.helpers({
         return Integrations.Const.WEBHOOK_TYPES;
       },
       integration(cond) {
    -    const boardId = Session.get('currentBoard') || Integrations.Const.GLOBAL_WEBHOOK_ID;
    +    const boardId = this.boardId();
         const condition = { boardId, ...cond };
         for (const k in condition) {
           if (!condition[k]) delete condition[k];
         }
         return ReactiveCache.getIntegration(condition);
       },
    -});
    -
    -Template.outgoingWebhooksPopup.events({
    -  'click a.flex'(evt, tpl) {
    -    tpl.disabled.set(!tpl.disabled.get());
    -    $(evt.target).toggleClass(CKCLS, tpl.disabled.get());
    +  onCreated() {
    +    this.disabled = new ReactiveVar(false);
       },
    -  submit(evt, tpl) {
    -    evt.preventDefault();
    -    const url = evt.target.url.value;
    -    const boardId = Session.get('currentBoard') || Integrations.Const.GLOBAL_WEBHOOK_ID;
    -    let id = null;
    -    let integration = null;
    -    const title = evt.target.title.value;
    -    const token = evt.target.token.value;
    -    const type = evt.target.type.value;
    -    const enabled = !tpl.disabled.get();
    -    let remove = false;
    -    const values = {
    -      url,
    -      type,
    -      token,
    -      title,
    -      enabled,
    -    };
    -
    -    const findIntegration = function(cond) {
    -      const condition = { boardId, ...cond };
    -      for (const k in condition) {
    -        if (!condition[k]) delete condition[k];
    -      }
    -      return ReactiveCache.getIntegration(condition);
    -    };
    -
    -    if (evt.target.id) {
    -      id = evt.target.id.value;
    -      integration = findIntegration({ _id: id });
    -      remove = !url;
    -    } else if (url) {
    -      integration = findIntegration({ url, token });
    -    }
    -    if (remove) {
    -      Integrations.remove(integration._id);
    -    } else if (integration && integration._id) {
    -      Integrations.update(integration._id, {
    -        $set: values,
    -      });
    -    } else if (url) {
    -      Integrations.insert({
    -        ...values,
    -        userId: Meteor.userId(),
    -        enabled: true,
    -        boardId,
    -        activities: ['all'],
    -      });
    -    }
    -    Popup.back();
    +  events() {
    +    return [
    +      {
    +        'click a.flex'(evt) {
    +          this.disabled.set(!this.disabled.get());
    +          $(evt.target).toggleClass(CKCLS, this.disabled.get());
    +        },
    +        submit(evt) {
    +          evt.preventDefault();
    +          const url = evt.target.url.value;
    +          const boardId = this.boardId();
    +          let id = null;
    +          let integration = null;
    +          const title = evt.target.title.value;
    +          const token = evt.target.token.value;
    +          const type = evt.target.type.value;
    +          const enabled = !this.disabled.get();
    +          let remove = false;
    +          const values = {
    +            url,
    +            type,
    +            token,
    +            title,
    +            enabled,
    +          };
    +          if (evt.target.id) {
    +            id = evt.target.id.value;
    +            integration = this.integration({ _id: id });
    +            remove = !url;
    +          } else if (url) {
    +            integration = this.integration({ url, token });
    +          }
    +          if (remove) {
    +            Integrations.remove(integration._id);
    +          } else if (integration && integration._id) {
    +            Integrations.update(integration._id, {
    +              $set: values,
    +            });
    +          } else if (url) {
    +            Integrations.insert({
    +              ...values,
    +              userId: Meteor.userId(),
    +              enabled: true,
    +              boardId,
    +              activities: ['all'],
    +            });
    +          }
    +          Popup.back();
    +        },
    +      },
    +    ];
       },
    -});
    +}).register('outgoingWebhooksPopup');
     
    -Template.exportBoardPopup.helpers({
    +BlazeComponent.extendComponent({
    +  template() {
    +    return 'chooseBoardSource';
    +  },
    +}).register('chooseBoardSourcePopup');
    +
    +BlazeComponent.extendComponent({
    +  template() {
    +    return 'exportBoard';
    +  },
       withApi() {
         return Template.instance().apiEnabled.get();
       },
    @@ -712,9 +683,9 @@ Template.exportBoardPopup.helpers({
         const boardId = Session.get('currentBoard');
         return `export-board-${boardId}.tsv`;
       },
    -});
    +}).register('exportBoardPopup');
     
    -Template.exportBoardPopup.events({
    +Template.exportBoard.events({
       'click .html-export-board': async event => {
         event.preventDefault();
         await ExportHtml(Popup)();
    @@ -776,42 +747,53 @@ function draggableMembersLabelsWidgets() {
     Template.membersWidget.onRendered(draggableMembersLabelsWidgets);
     Template.labelsWidget.onRendered(draggableMembersLabelsWidgets);
     
    -Template.boardChangeColorPopup.helpers({
    +BlazeComponent.extendComponent({
       backgroundColors() {
         return Boards.simpleSchema()._schema.color.allowedValues;
       },
    +
       isSelected() {
         const currentBoard = Utils.getCurrentBoard();
    -    return currentBoard.color === Template.currentData().toString();
    +    return currentBoard.color === this.currentData().toString();
       },
    -});
     
    -Template.boardChangeColorPopup.events({
    -  async 'click .js-select-background'(evt) {
    -    const currentBoard = Utils.getCurrentBoard();
    -    const newColor = Template.currentData().toString();
    -    await currentBoard.setColor(newColor);
    -    evt.preventDefault();
    +  events() {
    +    return [
    +      {
    +        'click .js-select-background'(evt) {
    +          const currentBoard = Utils.getCurrentBoard();
    +          const newColor = this.currentData().toString();
    +          currentBoard.setColor(newColor);
    +          evt.preventDefault();
    +        },
    +      },
    +    ];
       },
    -});
    +}).register('boardChangeColorPopup');
     
    -Template.boardChangeBackgroundImagePopup.events({
    -  async submit(event, tpl) {
    -    const currentBoard = Utils.getCurrentBoard();
    -    const backgroundImageURL = tpl.find('.js-board-background-image-url').value.trim();
    -    await currentBoard.setBackgroundImageURL(backgroundImageURL);
    -    Utils.setBackgroundImage();
    -    Popup.back();
    -    event.preventDefault();
    +BlazeComponent.extendComponent({
    +  events() {
    +    return [
    +      {
    +        submit(event) {
    +          const currentBoard = Utils.getCurrentBoard();
    +          const backgroundImageURL = this.find('.js-board-background-image-url').value.trim();
    +          currentBoard.setBackgroundImageURL(backgroundImageURL);
    +          Utils.setBackgroundImage();
    +          Popup.back();
    +          event.preventDefault();
    +        },
    +        'click .js-remove-background-image'() {
    +          const currentBoard = Utils.getCurrentBoard();
    +          currentBoard.setBackgroundImageURL("");
    +          Popup.back();
    +          Utils.reload();
    +          event.preventDefault();
    +        },
    +      },
    +    ];
       },
    -  'click .js-remove-background-image'() {
    -    const currentBoard = Utils.getCurrentBoard();
    -    currentBoard.setBackgroundImageURL("");
    -    Popup.back();
    -    Utils.reload();
    -    event.preventDefault();
    -  },
    -});
    +}).register('boardChangeBackgroundImagePopup');
     
     Template.boardChangeBackgroundImagePopup.helpers({
       backgroundImageURL() {
    @@ -820,67 +802,64 @@ Template.boardChangeBackgroundImagePopup.helpers({
       },
     });
     
    -Template.boardInfoOnMyBoardsPopup.onCreated(function() {
    -  this.currentBoard = Utils.getCurrentBoard();
    -});
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.currentBoard = Utils.getCurrentBoard();
    +  },
     
    -Template.boardInfoOnMyBoardsPopup.helpers({
    -  hideCardCounterList() {
    -    return Utils.isMiniScreen() && Session.get('currentBoard');
    -  },
    -  hideBoardMemberList() {
    -    return Utils.isMiniScreen() && Session.get('currentBoard');
    -  },
       allowsCardCounterList() {
    -    const tpl = Template.instance();
    -    return tpl.currentBoard.allowsCardCounterList;
    +    return this.currentBoard.allowsCardCounterList;
       },
    +
       allowsBoardMemberList() {
    -    const tpl = Template.instance();
    -    return tpl.currentBoard.allowsBoardMemberList;
    +    return this.currentBoard.allowsBoardMemberList;
       },
    -});
     
    -Template.boardInfoOnMyBoardsPopup.events({
    -  'click .js-field-has-cardcounterlist'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.currentBoard.allowsCardCounterList = !tpl.currentBoard
    -      .allowsCardCounterList;
    -      tpl.currentBoard.setAllowsCardCounterList(
    -        tpl.currentBoard.allowsCardCounterList,
    -    );
    -    $(`.js-field-has-cardcounterlist ${MCB}`).toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsCardCounterList,
    -    );
    -    $('.js-field-has-cardcounterlist').toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsCardCounterList,
    -    );
    +  events() {
    +    return [
    +      {
    +        'click .js-field-has-cardcounterlist'(evt) {
    +          evt.preventDefault();
    +          this.currentBoard.allowsCardCounterList = !this.currentBoard
    +            .allowsCardCounterList;
    +            this.currentBoard.setAllowsCardCounterList(
    +              this.currentBoard.allowsCardCounterList,
    +          );
    +          $(`.js-field-has-cardcounterlist ${MCB}`).toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsCardCounterList,
    +          );
    +          $('.js-field-has-cardcounterlist').toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsCardCounterList,
    +          );
    +        },
    +        'click .js-field-has-boardmemberlist'(evt) {
    +          evt.preventDefault();
    +          this.currentBoard.allowsBoardMemberList = !this.currentBoard
    +            .allowsBoardMemberList;
    +            this.currentBoard.setAllowsBoardMemberList(
    +              this.currentBoard.allowsBoardMemberList,
    +          );
    +          $(`.js-field-has-boardmemberlist ${MCB}`).toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsBoardMemberList,
    +          );
    +          $('.js-field-has-boardmemberlist').toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsBoardMemberList,
    +          );
    +        },
    +      },
    +    ];
       },
    -  'click .js-field-has-boardmemberlist'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.currentBoard.allowsBoardMemberList = !tpl.currentBoard
    -      .allowsBoardMemberList;
    -      tpl.currentBoard.setAllowsBoardMemberList(
    -        tpl.currentBoard.allowsBoardMemberList,
    -    );
    -    $(`.js-field-has-boardmemberlist ${MCB}`).toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsBoardMemberList,
    -    );
    -    $('.js-field-has-boardmemberlist').toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsBoardMemberList,
    -    );
    +}).register('boardInfoOnMyBoardsPopup');
    +
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.currentBoard = Utils.getCurrentBoard();
       },
    -});
     
    -Template.boardSubtaskSettingsPopup.onCreated(function() {
    -  this.currentBoard = Utils.getCurrentBoard();
    -});
    -
    -Template.boardSubtaskSettingsPopup.helpers({
       allowsSubtasks() {
         // Get the current board reactively using board ID from Session
         const boardId = Session.get('currentBoard');
    @@ -888,21 +867,22 @@ Template.boardSubtaskSettingsPopup.helpers({
         const result = currentBoard ? currentBoard.allowsSubtasks : false;
         return result;
       },
    +
       allowsReceivedDate() {
    -    const tpl = Template.instance();
    -    return tpl.currentBoard.allowsReceivedDate;
    +    return this.currentBoard.allowsReceivedDate;
       },
    +
       isBoardSelected() {
    -    const tpl = Template.instance();
    -    return tpl.currentBoard.subtasksDefaultBoardId === Template.currentData()._id;
    +    return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
       },
    +
       isNullBoardSelected() {
    -    const tpl = Template.instance();
         return (
    -      tpl.currentBoard.subtasksDefaultBoardId === null ||
    -      tpl.currentBoard.subtasksDefaultBoardId === undefined
    +      this.currentBoard.subtasksDefaultBoardId === null ||
    +      this.currentBoard.subtasksDefaultBoardId === undefined
         );
       },
    +
       boards() {
         const ret = ReactiveCache.getBoards(
           {
    @@ -915,11 +895,11 @@ Template.boardSubtaskSettingsPopup.helpers({
         );
         return ret;
       },
    +
       lists() {
    -    const tpl = Template.instance();
         return ReactiveCache.getLists(
           {
    -        boardId: tpl.currentBoard._id,
    +        boardId: this.currentBoard._id,
             archived: false,
           },
           {
    @@ -927,185 +907,203 @@ Template.boardSubtaskSettingsPopup.helpers({
           },
         );
       },
    +
       hasLists() {
    -    const tpl = Template.instance();
    -    const lists = ReactiveCache.getLists(
    -      {
    -        boardId: tpl.currentBoard._id,
    -        archived: false,
    -      },
    -      {
    -        sort: ['title'],
    -      },
    -    );
    -    return lists.length > 0;
    +    return this.lists().length > 0;
       },
    +
       isListSelected() {
    -    const tpl = Template.instance();
    -    return tpl.currentBoard.subtasksDefaultBoardId === Template.currentData()._id;
    +    return this.currentBoard.subtasksDefaultBoardId === this.currentData()._id;
       },
    +
       presentParentTask() {
         // Get the current board reactively using board ID from Session
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
    -
    +    
         let result = currentBoard ? currentBoard.presentParentTask : null;
         if (result === null || result === undefined) {
           result = 'no-parent';
         }
         return result;
       },
    -});
     
    -Template.boardSubtaskSettingsPopup.events({
    -  'click .js-field-has-subtasks'(evt, tpl) {
    -    evt.preventDefault();
    -    const newValue = !tpl.currentBoard.allowsSubtasks;
    -    Boards.update(tpl.currentBoard._id, { $set: { allowsSubtasks: newValue } });
    -    $('.js-field-deposit-board').prop(
    -      'disabled',
    -      !newValue,
    -    );
    +  events() {
    +    return [
    +      {
    +        'click .js-field-has-subtasks'(evt) {
    +          evt.preventDefault();
    +          const newValue = !this.currentBoard.allowsSubtasks;
    +          Boards.update(this.currentBoard._id, { $set: { allowsSubtasks: newValue } });
    +          $('.js-field-deposit-board').prop(
    +            'disabled',
    +            !newValue,
    +          );
    +        },
    +        'change .js-field-deposit-board'(evt) {
    +          let value = evt.target.value;
    +          if (value === 'null') {
    +            value = null;
    +          }
    +          this.currentBoard.setSubtasksDefaultBoardId(value);
    +          evt.preventDefault();
    +        },
    +        'change .js-field-deposit-list'(evt) {
    +          this.currentBoard.setSubtasksDefaultListId(evt.target.value);
    +          evt.preventDefault();
    +        },
    +        'click .js-field-show-parent-in-minicard'(evt) {
    +          // Get the ID from the anchor element, not the span
    +          const anchorElement = $(evt.target).closest('.js-field-show-parent-in-minicard')[0];
    +          const value = anchorElement ? anchorElement.id : null;
    +          
    +          if (value) {
    +            Boards.update(this.currentBoard._id, { $set: { presentParentTask: value } });
    +          }
    +          evt.preventDefault();
    +        },
    +      },
    +    ];
       },
    -  'change .js-field-deposit-board'(evt, tpl) {
    -    let value = evt.target.value;
    -    if (value === 'null') {
    -      value = null;
    -    }
    -    tpl.currentBoard.setSubtasksDefaultBoardId(value);
    -    evt.preventDefault();
    -  },
    -  'change .js-field-deposit-list'(evt, tpl) {
    -    tpl.currentBoard.setSubtasksDefaultListId(evt.target.value);
    -    evt.preventDefault();
    -  },
    -  'click .js-field-show-parent-in-minicard'(evt, tpl) {
    -    // Get the ID from the anchor element, not the span
    -    const anchorElement = $(evt.target).closest('.js-field-show-parent-in-minicard')[0];
    -    const value = anchorElement ? anchorElement.id : null;
    +}).register('boardSubtaskSettingsPopup');
     
    -    if (value) {
    -      Boards.update(tpl.currentBoard._id, { $set: { presentParentTask: value } });
    -    }
    -    evt.preventDefault();
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.currentBoard = Utils.getCurrentBoard();
       },
    -});
     
    -Template.boardCardSettingsPopup.onCreated(function() {
    -  this.currentBoard = Utils.getCurrentBoard();
    -});
    -
    -Template.boardCardSettingsPopup.helpers({
       allowsReceivedDate() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsReceivedDate : false;
       },
    +
       allowsStartDate() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsStartDate : false;
       },
    +
       allowsDueDate() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsDueDate : false;
       },
    +
       allowsEndDate() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsEndDate : false;
       },
    +
       allowsSubtasks() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsSubtasks : false;
       },
    +
       allowsCreator() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? (currentBoard.allowsCreator ?? false) : false;
       },
    +
       allowsCreatorOnMinicard() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? (currentBoard.allowsCreatorOnMinicard ?? false) : false;
       },
    +
       allowsMembers() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsMembers : false;
       },
    +
       allowsAssignee() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsAssignee : false;
       },
    +
       allowsAssignedBy() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsAssignedBy : false;
       },
    +
       allowsRequestedBy() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsRequestedBy : false;
       },
    +
       allowsCardSortingByNumber() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsCardSortingByNumber : false;
       },
    +
       allowsShowLists() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsShowLists : false;
       },
    +
       allowsLabels() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsLabels : false;
       },
    +
       allowsShowListsOnMinicard() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsShowListsOnMinicard : false;
       },
    +
       allowsChecklists() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsChecklists : false;
       },
    +
       allowsAttachments() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsAttachments : false;
       },
    +
       allowsComments() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsComments : false;
       },
    +
       allowsCardNumber() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsCardNumber : false;
       },
    +
       allowsDescriptionTitle() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsDescriptionTitle : false;
       },
    +
       allowsDescriptionText() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsDescriptionText : false;
       },
    +
       isBoardSelected() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.dateSettingsDefaultBoardID : false;
       },
    +
       isNullBoardSelected() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
    @@ -1114,26 +1112,31 @@ Template.boardCardSettingsPopup.helpers({
           currentBoard.dateSettingsDefaultBoardId === undefined
         ) : true;
       },
    +
       allowsDescriptionTextOnMinicard() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsDescriptionTextOnMinicard : false;
       },
    +
       allowsCoverAttachmentOnMinicard() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsCoverAttachmentOnMinicard : false;
       },
    +
       allowsBadgeAttachmentOnMinicard() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsBadgeAttachmentOnMinicard : false;
       },
    +
       allowsCardSortingByNumberOnMinicard() {
         const boardId = Session.get('currentBoard');
         const currentBoard = ReactiveCache.getBoard(boardId);
         return currentBoard ? currentBoard.allowsCardSortingByNumberOnMinicard : false;
       },
    +
       boards() {
         const ret = ReactiveCache.getBoards(
           {
    @@ -1146,11 +1149,11 @@ Template.boardCardSettingsPopup.helpers({
         );
         return ret;
       },
    +
       lists() {
    -    const tpl = Template.instance();
         return ReactiveCache.getLists(
           {
    -        boardId: tpl.currentBoard._id,
    +        boardId: this.currentBoard._id,
             archived: false,
           },
           {
    @@ -1158,288 +1161,283 @@ Template.boardCardSettingsPopup.helpers({
           },
         );
       },
    -  hasLists() {
    -    const tpl = Template.instance();
    -    const lists = ReactiveCache.getLists(
    -      {
    -        boardId: tpl.currentBoard._id,
    -        archived: false,
    -      },
    -      {
    -        sort: ['title'],
    -      },
    -    );
    -    return lists.length > 0;
    -  },
    -  isListSelected() {
    -    const tpl = Template.instance();
    -    return (
    -      tpl.currentBoard.dateSettingsDefaultBoardId === Template.currentData()._id
    -    );
    -  },
    -});
     
    -Template.boardCardSettingsPopup.events({
    -  'click .js-field-has-receiveddate'(evt, tpl) {
    -    evt.preventDefault();
    -    const newValue = !tpl.currentBoard.allowsReceivedDate;
    -    Boards.update(tpl.currentBoard._id, { $set: { allowsReceivedDate: newValue } });
    +  hasLists() {
    +    return this.lists().length > 0;
       },
    -  'click .js-field-has-startdate'(evt, tpl) {
    -    evt.preventDefault();
    -    const newValue = !tpl.currentBoard.allowsStartDate;
    -    Boards.update(tpl.currentBoard._id, { $set: { allowsStartDate: newValue } });
    -  },
    -  'click .js-field-has-enddate'(evt, tpl) {
    -    evt.preventDefault();
    -    const newValue = !tpl.currentBoard.allowsEndDate;
    -    Boards.update(tpl.currentBoard._id, { $set: { allowsEndDate: newValue } });
    -  },
    -  'click .js-field-has-duedate'(evt, tpl) {
    -    evt.preventDefault();
    -    const newValue = !tpl.currentBoard.allowsDueDate;
    -    Boards.update(tpl.currentBoard._id, { $set: { allowsDueDate: newValue } });
    -  },
    -  'click .js-field-has-subtasks'(evt, tpl) {
    -    evt.preventDefault();
    -    const newValue = !tpl.currentBoard.allowsSubtasks;
    -    Boards.update(tpl.currentBoard._id, { $set: { allowsSubtasks: newValue } });
    -  },
    -  'click .js-field-has-creator'(evt, tpl) {
    -    evt.preventDefault();
    -    const newValue = !tpl.currentBoard.allowsCreator;
    -    Boards.update(tpl.currentBoard._id, { $set: { allowsCreator: newValue } });
    -  },
    -  'click .js-field-has-creator-on-minicard'(evt, tpl) {
    -    evt.preventDefault();
    -    const newValue = !tpl.currentBoard.allowsCreatorOnMinicard;
    -    Boards.update(tpl.currentBoard._id, { $set: { allowsCreatorOnMinicard: newValue } });
    -  },
    -  'click .js-field-has-members'(evt, tpl) {
    -    evt.preventDefault();
    -    const newValue = !tpl.currentBoard.allowsMembers;
    -    Boards.update(tpl.currentBoard._id, { $set: { allowsMembers: newValue } });
    -  },
    -  'click .js-field-has-assignee'(evt, tpl) {
    -    evt.preventDefault();
    -    const newValue = !tpl.currentBoard.allowsAssignee;
    -    Boards.update(tpl.currentBoard._id, { $set: { allowsAssignee: newValue } });
    -  },
    -  'click .js-field-has-assigned-by'(evt, tpl) {
    -    evt.preventDefault();
    -    const newValue = !tpl.currentBoard.allowsAssignedBy;
    -    Boards.update(tpl.currentBoard._id, { $set: { allowsAssignedBy: newValue } });
    -  },
    -  'click .js-field-has-requested-by'(evt, tpl) {
    -    evt.preventDefault();
    -    const newValue = !tpl.currentBoard.allowsRequestedBy;
    -    Boards.update(tpl.currentBoard._id, { $set: { allowsRequestedBy: newValue } });
    -  },
    -  'click .js-field-has-card-sorting-by-number'(evt, tpl) {
    -    evt.preventDefault();
    -    const newValue = !tpl.currentBoard.allowsCardSortingByNumber;
    -    Boards.update(tpl.currentBoard._id, { $set: { allowsCardSortingByNumber: newValue } });
    -  },
    -  'click .js-field-has-card-show-lists'(evt, tpl) {
    -    evt.preventDefault();
    -    const newValue = !tpl.currentBoard.allowsShowLists;
    -    Boards.update(tpl.currentBoard._id, { $set: { allowsShowLists: newValue } });
    -  },
    -  'click .js-field-has-labels'(evt, tpl) {
    -    evt.preventDefault();
    -    const newValue = !tpl.currentBoard.allowsLabels;
    -    Boards.update(tpl.currentBoard._id, { $set: { allowsLabels: newValue } });
    -  },
    -  'click .js-field-has-card-show-lists-on-minicard'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.currentBoard.allowsShowListsOnMinicard = !tpl.currentBoard
    -      .allowsShowListsOnMinicard;
    -    tpl.currentBoard.setAllowsShowListsOnMinicard(
    -      tpl.currentBoard.allowsShowListsOnMinicard,
    -    );
    -    $(`.js-field-has-card-show-lists-on-minicard ${MCB}`).toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsShowListsOnMinicard,
    -    );
    -    $('.js-field-has-card-show-lists-on-minicard').toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsShowListsOnMinicard,
    +
    +  isListSelected() {
    +    return (
    +      this.currentBoard.dateSettingsDefaultBoardId === this.currentData()._id
         );
       },
    -  'click .js-field-has-description-title'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.currentBoard.allowsDescriptionTitle = !tpl.currentBoard
    -      .allowsDescriptionTitle;
    -    tpl.currentBoard.setAllowsDescriptionTitle(
    -      tpl.currentBoard.allowsDescriptionTitle,
    -    );
    -    $(`.js-field-has-description-title ${MCB}`).toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsDescriptionTitle,
    -    );
    -    $('.js-field-has-description-title').toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsDescriptionTitle,
    -    );
    +
    +  events() {
    +    return [
    +      {
    +        'click .js-field-has-receiveddate'(evt) {
    +          evt.preventDefault();
    +          const newValue = !this.currentBoard.allowsReceivedDate;
    +          Boards.update(this.currentBoard._id, { $set: { allowsReceivedDate: newValue } });
    +        },
    +        'click .js-field-has-startdate'(evt) {
    +          evt.preventDefault();
    +          const newValue = !this.currentBoard.allowsStartDate;
    +          Boards.update(this.currentBoard._id, { $set: { allowsStartDate: newValue } });
    +        },
    +        'click .js-field-has-enddate'(evt) {
    +          evt.preventDefault();
    +          const newValue = !this.currentBoard.allowsEndDate;
    +          Boards.update(this.currentBoard._id, { $set: { allowsEndDate: newValue } });
    +        },
    +        'click .js-field-has-duedate'(evt) {
    +          evt.preventDefault();
    +          const newValue = !this.currentBoard.allowsDueDate;
    +          Boards.update(this.currentBoard._id, { $set: { allowsDueDate: newValue } });
    +        },
    +        'click .js-field-has-subtasks'(evt) {
    +          evt.preventDefault();
    +          const newValue = !this.currentBoard.allowsSubtasks;
    +          Boards.update(this.currentBoard._id, { $set: { allowsSubtasks: newValue } });
    +        },
    +        'click .js-field-has-creator'(evt) {
    +          evt.preventDefault();
    +          const newValue = !this.currentBoard.allowsCreator;
    +          Boards.update(this.currentBoard._id, { $set: { allowsCreator: newValue } });
    +        },
    +        'click .js-field-has-creator-on-minicard'(evt) {
    +          evt.preventDefault();
    +          const newValue = !this.currentBoard.allowsCreatorOnMinicard;
    +          Boards.update(this.currentBoard._id, { $set: { allowsCreatorOnMinicard: newValue } });
    +        },
    +        'click .js-field-has-members'(evt) {
    +          evt.preventDefault();
    +          const newValue = !this.currentBoard.allowsMembers;
    +          Boards.update(this.currentBoard._id, { $set: { allowsMembers: newValue } });
    +        },
    +        'click .js-field-has-assignee'(evt) {
    +          evt.preventDefault();
    +          const newValue = !this.currentBoard.allowsAssignee;
    +          Boards.update(this.currentBoard._id, { $set: { allowsAssignee: newValue } });
    +        },
    +        'click .js-field-has-assigned-by'(evt) {
    +          evt.preventDefault();
    +          const newValue = !this.currentBoard.allowsAssignedBy;
    +          Boards.update(this.currentBoard._id, { $set: { allowsAssignedBy: newValue } });
    +        },
    +        'click .js-field-has-requested-by'(evt) {
    +          evt.preventDefault();
    +          const newValue = !this.currentBoard.allowsRequestedBy;
    +          Boards.update(this.currentBoard._id, { $set: { allowsRequestedBy: newValue } });
    +        },
    +        'click .js-field-has-card-sorting-by-number'(evt) {
    +          evt.preventDefault();
    +          const newValue = !this.currentBoard.allowsCardSortingByNumber;
    +          Boards.update(this.currentBoard._id, { $set: { allowsCardSortingByNumber: newValue } });
    +        },
    +        'click .js-field-has-card-show-lists'(evt) {
    +          evt.preventDefault();
    +          const newValue = !this.currentBoard.allowsShowLists;
    +          Boards.update(this.currentBoard._id, { $set: { allowsShowLists: newValue } });
    +        },
    +        'click .js-field-has-labels'(evt) {
    +          evt.preventDefault();
    +          const newValue = !this.currentBoard.allowsLabels;
    +          Boards.update(this.currentBoard._id, { $set: { allowsLabels: newValue } });
    +        },
    +        'click .js-field-has-card-show-lists-on-minicard'(evt) {
    +          evt.preventDefault();
    +          this.currentBoard.allowsShowListsOnMinicard = !this.currentBoard
    +            .allowsShowListsOnMinicard;
    +          this.currentBoard.setAllowsShowListsOnMinicard(
    +            this.currentBoard.allowsShowListsOnMinicard,
    +          );
    +          $(`.js-field-has-card-show-lists-on-minicard ${MCB}`).toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsShowListsOnMinicard,
    +          );
    +          $('.js-field-has-card-show-lists-on-minicard').toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsShowListsOnMinicard,
    +          );
    +        },
    +        'click .js-field-has-description-title'(evt) {
    +          evt.preventDefault();
    +          this.currentBoard.allowsDescriptionTitle = !this.currentBoard
    +            .allowsDescriptionTitle;
    +          this.currentBoard.setAllowsDescriptionTitle(
    +            this.currentBoard.allowsDescriptionTitle,
    +          );
    +          $(`.js-field-has-description-title ${MCB}`).toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsDescriptionTitle,
    +          );
    +          $('.js-field-has-description-title').toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsDescriptionTitle,
    +          );
    +        },
    +        'click .js-field-has-card-number'(evt) {
    +          evt.preventDefault();
    +          this.currentBoard.allowsCardNumber = !this.currentBoard
    +            .allowsCardNumber;
    +          this.currentBoard.setAllowsCardNumber(
    +            this.currentBoard.allowsCardNumber,
    +          );
    +          $(`.js-field-has-card-number ${MCB}`).toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsCardNumber,
    +          );
    +          $('.js-field-has-card-number').toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsCardNumber,
    +          );
    +        },
    +        'click .js-field-has-description-text-on-minicard'(evt) {
    +          evt.preventDefault();
    +          this.currentBoard.allowsDescriptionTextOnMinicard = !this.currentBoard
    +            .allowsDescriptionTextOnMinicard;
    +          this.currentBoard.setallowsDescriptionTextOnMinicard(
    +            this.currentBoard.allowsDescriptionTextOnMinicard,
    +          );
    +          $(`.js-field-has-description-text-on-minicard ${MCB}`).toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsDescriptionTextOnMinicard,
    +          );
    +          $('.js-field-has-description-text-on-minicard').toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsDescriptionTextOnMinicard,
    +          );
    +        },
    +        'click .js-field-has-description-text'(evt) {
    +          evt.preventDefault();
    +          this.currentBoard.allowsDescriptionText = !this.currentBoard
    +            .allowsDescriptionText;
    +          this.currentBoard.setAllowsDescriptionText(
    +            this.currentBoard.allowsDescriptionText,
    +          );
    +          $(`.js-field-has-description-text ${MCB}`).toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsDescriptionText,
    +          );
    +          $('.js-field-has-description-text').toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsDescriptionText,
    +          );
    +        },
    +        'click .js-field-has-checklists'(evt) {
    +          evt.preventDefault();
    +          this.currentBoard.allowsChecklists = !this.currentBoard
    +            .allowsChecklists;
    +          this.currentBoard.setAllowsChecklists(
    +            this.currentBoard.allowsChecklists,
    +          );
    +          $(`.js-field-has-checklists ${MCB}`).toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsChecklists,
    +          );
    +          $('.js-field-has-checklists').toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsChecklists,
    +          );
    +        },
    +        'click .js-field-has-attachments'(evt) {
    +          evt.preventDefault();
    +          this.currentBoard.allowsAttachments = !this.currentBoard
    +            .allowsAttachments;
    +          this.currentBoard.setAllowsAttachments(
    +            this.currentBoard.allowsAttachments,
    +          );
    +          $(`.js-field-has-attachments ${MCB}`).toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsAttachments,
    +          );
    +          $('.js-field-has-attachments').toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsAttachments,
    +          );
    +        },
    +        'click .js-field-has-comments'(evt) {
    +          evt.preventDefault();
    +          this.currentBoard.allowsComments = !this.currentBoard.allowsComments;
    +          this.currentBoard.setAllowsComments(this.currentBoard.allowsComments);
    +          $(`.js-field-has-comments ${MCB}`).toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsComments,
    +          );
    +          $('.js-field-has-comments').toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsComments,
    +          );
    +        },
    +        'click .js-field-has-activities'(evt) {
    +          evt.preventDefault();
    +          this.currentBoard.allowsActivities = !this.currentBoard
    +            .allowsActivities;
    +          this.currentBoard.setAllowsActivities(
    +            this.currentBoard.allowsActivities,
    +          );
    +          $(`.js-field-has-activities ${MCB}`).toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsActivities,
    +          );
    +          $('.js-field-has-activities').toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsActivities,
    +          );
    +        },
    +        'click .js-field-has-cover-attachment-on-minicard'(evt) {
    +          evt.preventDefault();
    +          this.currentBoard.allowsCoverAttachmentOnMinicard = !this.currentBoard
    +            .allowsCoverAttachmentOnMinicard;
    +          this.currentBoard.setallowsCoverAttachmentOnMinicard(
    +            this.currentBoard.allowsCoverAttachmentOnMinicard,
    +          );
    +          $(`.js-field-has-cover-attachment-on-minicard ${MCB}`).toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsCoverAttachmentOnMinicard,
    +          );
    +          $('.js-field-has-cover-attachment-on-minicard').toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsCoverAttachmentOnMinicard,
    +          );
    +        },
    +        'click .js-field-has-badge-attachment-on-minicard'(evt) {
    +          evt.preventDefault();
    +          this.currentBoard.allowsBadgeAttachmentOnMinicard = !this.currentBoard
    +            .allowsBadgeAttachmentOnMinicard;
    +          this.currentBoard.setallowsBadgeAttachmentOnMinicard(
    +            this.currentBoard.allowsBadgeAttachmentOnMinicard,
    +          );
    +          $(`.js-field-has-badge-attachment-on-minicard ${MCB}`).toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsBadgeAttachmentOnMinicard,
    +          );
    +          $('.js-field-has-badge-attachment-on-minicard').toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsBadgeAttachmentOnMinicard,
    +          );
    +        },
    +        'click .js-field-has-card-sorting-by-number-on-minicard'(evt) {
    +          evt.preventDefault();
    +          this.currentBoard.allowsCardSortingByNumberOnMinicard = !this.currentBoard
    +            .allowsCardSortingByNumberOnMinicard;
    +          this.currentBoard.setallowsCardSortingByNumberOnMinicard(
    +            this.currentBoard.allowsCardSortingByNumberOnMinicard,
    +          );
    +          $(`.js-field-has-card-sorting-by-number-on-minicard ${MCB}`).toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsCardSortingByNumberOnMinicard,
    +          );
    +          $('.js-field-has-card-sorting-by-number-on-minicard').toggleClass(
    +            CKCLS,
    +            this.currentBoard.allowsCardSortingByNumberOnMinicard,
    +          );
    +        },
    +      },
    +    ];
       },
    -  'click .js-field-has-card-number'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.currentBoard.allowsCardNumber = !tpl.currentBoard
    -      .allowsCardNumber;
    -    tpl.currentBoard.setAllowsCardNumber(
    -      tpl.currentBoard.allowsCardNumber,
    -    );
    -    $(`.js-field-has-card-number ${MCB}`).toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsCardNumber,
    -    );
    -    $('.js-field-has-card-number').toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsCardNumber,
    -    );
    -  },
    -  'click .js-field-has-description-text-on-minicard'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.currentBoard.allowsDescriptionTextOnMinicard = !tpl.currentBoard
    -      .allowsDescriptionTextOnMinicard;
    -    tpl.currentBoard.setallowsDescriptionTextOnMinicard(
    -      tpl.currentBoard.allowsDescriptionTextOnMinicard,
    -    );
    -    $(`.js-field-has-description-text-on-minicard ${MCB}`).toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsDescriptionTextOnMinicard,
    -    );
    -    $('.js-field-has-description-text-on-minicard').toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsDescriptionTextOnMinicard,
    -    );
    -  },
    -  'click .js-field-has-description-text'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.currentBoard.allowsDescriptionText = !tpl.currentBoard
    -      .allowsDescriptionText;
    -    tpl.currentBoard.setAllowsDescriptionText(
    -      tpl.currentBoard.allowsDescriptionText,
    -    );
    -    $(`.js-field-has-description-text ${MCB}`).toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsDescriptionText,
    -    );
    -    $('.js-field-has-description-text').toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsDescriptionText,
    -    );
    -  },
    -  'click .js-field-has-checklists'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.currentBoard.allowsChecklists = !tpl.currentBoard
    -      .allowsChecklists;
    -    tpl.currentBoard.setAllowsChecklists(
    -      tpl.currentBoard.allowsChecklists,
    -    );
    -    $(`.js-field-has-checklists ${MCB}`).toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsChecklists,
    -    );
    -    $('.js-field-has-checklists').toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsChecklists,
    -    );
    -  },
    -  'click .js-field-has-attachments'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.currentBoard.allowsAttachments = !tpl.currentBoard
    -      .allowsAttachments;
    -    tpl.currentBoard.setAllowsAttachments(
    -      tpl.currentBoard.allowsAttachments,
    -    );
    -    $(`.js-field-has-attachments ${MCB}`).toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsAttachments,
    -    );
    -    $('.js-field-has-attachments').toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsAttachments,
    -    );
    -  },
    -  'click .js-field-has-comments'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.currentBoard.allowsComments = !tpl.currentBoard.allowsComments;
    -    tpl.currentBoard.setAllowsComments(tpl.currentBoard.allowsComments);
    -    $(`.js-field-has-comments ${MCB}`).toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsComments,
    -    );
    -    $('.js-field-has-comments').toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsComments,
    -    );
    -  },
    -  'click .js-field-has-activities'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.currentBoard.allowsActivities = !tpl.currentBoard
    -      .allowsActivities;
    -    tpl.currentBoard.setAllowsActivities(
    -      tpl.currentBoard.allowsActivities,
    -    );
    -    $(`.js-field-has-activities ${MCB}`).toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsActivities,
    -    );
    -    $('.js-field-has-activities').toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsActivities,
    -    );
    -  },
    -  'click .js-field-has-cover-attachment-on-minicard'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.currentBoard.allowsCoverAttachmentOnMinicard = !tpl.currentBoard
    -      .allowsCoverAttachmentOnMinicard;
    -    tpl.currentBoard.setallowsCoverAttachmentOnMinicard(
    -      tpl.currentBoard.allowsCoverAttachmentOnMinicard,
    -    );
    -    $(`.js-field-has-cover-attachment-on-minicard ${MCB}`).toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsCoverAttachmentOnMinicard,
    -    );
    -    $('.js-field-has-cover-attachment-on-minicard').toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsCoverAttachmentOnMinicard,
    -    );
    -  },
    -  'click .js-field-has-badge-attachment-on-minicard'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.currentBoard.allowsBadgeAttachmentOnMinicard = !tpl.currentBoard
    -      .allowsBadgeAttachmentOnMinicard;
    -    tpl.currentBoard.setallowsBadgeAttachmentOnMinicard(
    -      tpl.currentBoard.allowsBadgeAttachmentOnMinicard,
    -    );
    -    $(`.js-field-has-badge-attachment-on-minicard ${MCB}`).toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsBadgeAttachmentOnMinicard,
    -    );
    -    $('.js-field-has-badge-attachment-on-minicard').toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsBadgeAttachmentOnMinicard,
    -    );
    -  },
    -  'click .js-field-has-card-sorting-by-number-on-minicard'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.currentBoard.allowsCardSortingByNumberOnMinicard = !tpl.currentBoard
    -      .allowsCardSortingByNumberOnMinicard;
    -    tpl.currentBoard.setallowsCardSortingByNumberOnMinicard(
    -      tpl.currentBoard.allowsCardSortingByNumberOnMinicard,
    -    );
    -    $(`.js-field-has-card-sorting-by-number-on-minicard ${MCB}`).toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsCardSortingByNumberOnMinicard,
    -    );
    -    $('.js-field-has-card-sorting-by-number-on-minicard').toggleClass(
    -      CKCLS,
    -      tpl.currentBoard.allowsCardSortingByNumberOnMinicard,
    -    );
    -  },
    -});
    +}).register('boardCardSettingsPopup');
     
     // Use Session variables instead of global ReactiveVars
     Session.setDefault('addMemberPopup.searchResults', []);
    @@ -1449,32 +1447,40 @@ Session.setDefault('addMemberPopup.loading', false);
     Session.setDefault('addMemberPopup.error', '');
     
     
    -Template.addMemberPopup.onCreated(function() {
    -  // Use Session variables
    -  this.searchTimeout = null;
    -  Session.set('addMemberPopup.searchResults', []);
    -  Session.set('addMemberPopup.searching', false);
    -  Session.set('addMemberPopup.noResults', false);
    -  Session.set('addMemberPopup.loading', false);
    -  Session.set('addMemberPopup.error', '');
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    // Use Session variables
    +    this.searchTimeout = null;
    +  },
     
    -  this.setError = function(error) {
    -    Session.set('addMemberPopup.error', error);
    -  };
    +  onRendered() {
    +    this.find('.js-search-member-input').focus();
    +    this.setLoading(false);
    +  },
     
    -  this.setLoading = function(w) {
    -    Session.set('addMemberPopup.loading', w);
    -  };
    +  isBoardMember() {
    +    const userId = this.currentData()._id;
    +    const user = ReactiveCache.getUser(userId);
    +    return user && user.isBoardMember();
    +  },
     
    -  this.isLoading = function() {
    -    return Session.get('addMemberPopup.loading');
    -  };
    -
    -  this.isValidEmail = function(email) {
    +  isValidEmail(email) {
         return SimpleSchema.RegEx.Email.test(email);
    -  };
    +  },
     
    -  this.performSearch = function(query) {
    +  setError(error) {
    +    Session.set('addMemberPopup.error', error);
    +  },
    +
    +  setLoading(w) {
    +    Session.set('addMemberPopup.loading', w);
    +  },
    +
    +  isLoading() {
    +    return Session.get('addMemberPopup.loading');
    +  },
    +
    +  performSearch(query) {
         if (!query || query.length < 2) {
           Session.set('addMemberPopup.searchResults', []);
           Session.set('addMemberPopup.noResults', false);
    @@ -1484,80 +1490,68 @@ Template.addMemberPopup.onCreated(function() {
         Session.set('addMemberPopup.searching', true);
         Session.set('addMemberPopup.noResults', false);
     
    -    const boardId = Session.get('currentBoard');
    -    Meteor.call('searchUsers', query, boardId, (error, results) => {
    -      Session.set('addMemberPopup.searching', false);
    -      if (error) {
    -        console.error('Search error:', error);
    -        Session.set('addMemberPopup.searchResults', []);
    -        Session.set('addMemberPopup.noResults', true);
    -      } else {
    -        Session.set('addMemberPopup.searchResults', results);
    -        if (results.length === 0) {
    -          Session.set('addMemberPopup.noResults', true);
    -        }
    -      }
    -    });
    -  };
    +    // Use the fallback search
    +    const results = UserSearchIndex.search(query, { limit: 20 }).fetch();
    +    Session.set('addMemberPopup.searchResults', results);
    +    Session.set('addMemberPopup.searching', false);
    +    
    +    if (results.length === 0) {
    +      Session.set('addMemberPopup.noResults', true);
    +    }
    +  },
     
    -  this.inviteUser = function(idNameEmail) {
    +  inviteUser(idNameEmail) {
         const boardId = Session.get('currentBoard');
         this.setLoading(true);
         const self = this;
         Meteor.call('inviteUserToBoard', idNameEmail, boardId, (err, ret) => {
           self.setLoading(false);
    -      if (err) {
    -        self.setError(err.error);
    -      } else {
    -        Popup.back();
    -      }
    +      if (err) self.setError(err.error);
    +      else if (ret.email) self.setError('email-sent');
    +      else Popup.back();
         });
    -  };
    -});
    -
    -Template.addMemberPopup.onDestroyed(function() {
    -  if (this.searchTimeout) {
    -    clearTimeout(this.searchTimeout);
    -  }
    -  Session.set('addMemberPopup.searching', false);
    -  Session.set('addMemberPopup.loading', false);
    -});
    -
    -Template.addMemberPopup.onRendered(function() {
    -  this.find('.js-search-member-input').focus();
    -  this.setLoading(false);
    -});
    -
    -Template.addMemberPopup.events({
    -  'keyup .js-search-member-input'(event, tpl) {
    -    Session.set('addMemberPopup.error', '');
    -    const query = event.target.value.trim();
    -
    -    // Clear previous timeout
    -    if (tpl.searchTimeout) {
    -      clearTimeout(tpl.searchTimeout);
    -    }
    -
    -    // Debounce search
    -    tpl.searchTimeout = setTimeout(() => {
    -      tpl.performSearch(query);
    -    }, 300);
       },
    -  'click .js-select-member'(event, tpl) {
    -    const userId = Template.currentData()._id;
    -    tpl.inviteUser(userId);
    +
    +  events() {
    +    return [
    +      {
    +        'keyup .js-search-member-input'(event) {
    +          this.setError('');
    +          const query = event.target.value.trim();
    +          this.searchQuery.set(query);
    +          
    +          // Clear previous timeout
    +          if (this.searchTimeout) {
    +            clearTimeout(this.searchTimeout);
    +          }
    +          
    +          // Debounce search
    +          this.searchTimeout = setTimeout(() => {
    +            this.performSearch(query);
    +          }, 300);
    +        },
    +        'click .js-select-member'() {
    +          const userId = this.currentData()._id;
    +          const currentBoard = Utils.getCurrentBoard();
    +          if (!currentBoard.hasMember(userId)) {
    +            this.inviteUser(userId);
    +          }
    +        },
    +        'click .js-email-invite'() {
    +          const idNameEmail = $('.js-search-member-input').val();
    +          if (idNameEmail.indexOf('@') < 0 || this.isValidEmail(idNameEmail)) {
    +            this.inviteUser(idNameEmail);
    +          } else this.setError('email-invalid');
    +        },
    +      },
    +    ];
       },
    -  'click .js-email-invite'(event, tpl) {
    -    const idNameEmail = $('.js-search-member-input').val();
    -    if (idNameEmail.indexOf('@') < 0 || tpl.isValidEmail(idNameEmail)) {
    -      tpl.inviteUser(idNameEmail);
    -    } else Session.set('addMemberPopup.error', 'email-invalid');
    -  },
    -});
    +}).register('addMemberPopup');
     
     Template.addMemberPopup.helpers({
       searchResults() {
         const results = Session.get('addMemberPopup.searchResults');
    +    console.log('searchResults helper called, returning:', results);
         return results;
       },
       searching() {
    @@ -1567,52 +1561,90 @@ Template.addMemberPopup.helpers({
         return Session.get('addMemberPopup.noResults');
       },
       loading() {
    -    return { get() { return Session.get('addMemberPopup.loading'); } };
    +    return Session.get('addMemberPopup.loading');
       },
       error() {
    -    return { get() { return Session.get('addMemberPopup.error'); } };
    +    return Session.get('addMemberPopup.error');
       },
       isBoardMember() {
         const userId = this._id;
    -    const boardId = Session.get('currentBoard');
    -    const board = ReactiveCache.getBoard(boardId);
    -    return board && board.hasMember(userId);
    +    const user = ReactiveCache.getUser(userId);
    +    return user && user.isBoardMember();
       }
     })
     
     Template.addMemberPopupTest.helpers({
       searchResults() {
    +    console.log('addMemberPopupTest searchResults helper called');
         return Session.get('addMemberPopup.searchResults') || [];
       }
     })
     
    -Template.addBoardOrgPopup.onCreated(function() {
    -  this.error = new ReactiveVar('');
    -  this.loading = new ReactiveVar(false);
    -  this.findOrgsOptions = new ReactiveVar({});
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.error = new ReactiveVar('');
    +    this.loading = new ReactiveVar(false);
    +    this.findOrgsOptions = new ReactiveVar({});
     
    -  this.page = new ReactiveVar(1);
    -  this.autorun(() => {
    -    const limitOrgs = this.page.get() * Number.MAX_SAFE_INTEGER;
    -    this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => {});
    -  });
    +    this.page = new ReactiveVar(1);
    +    this.autorun(() => {
    +      const limitOrgs = this.page.get() * Number.MAX_SAFE_INTEGER;
    +      this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => {});
    +    });
    +  },
     
    -  this.setError = function(error) {
    +  onRendered() {
    +    this.setLoading(false);
    +  },
    +
    +  setError(error) {
         this.error.set(error);
    -  };
    +  },
     
    -  this.setLoading = function(w) {
    +  setLoading(w) {
         this.loading.set(w);
    -  };
    +  },
     
    -  this.isLoading = function() {
    +  isLoading() {
         return this.loading.get();
    -  };
    -});
    +  },
     
    -Template.addBoardOrgPopup.onRendered(function() {
    -  this.setLoading(false);
    -});
    +  events() {
    +    return [
    +      {
    +        'keyup input'() {
    +          this.setError('');
    +        },
    +        'change #jsBoardOrgs'() {
    +          let currentBoard = Utils.getCurrentBoard();
    +          let selectElt = document.getElementById("jsBoardOrgs");
    +          let selectedOrgId = selectElt.options[selectElt.selectedIndex].value;
    +          let selectedOrgDisplayName = selectElt.options[selectElt.selectedIndex].text;
    +          let boardOrganizations = [];
    +          if(currentBoard.orgs !== undefined){
    +            for(let i = 0; i < currentBoard.orgs.length; i++){
    +              boardOrganizations.push(currentBoard.orgs[i]);
    +            }
    +          }
    +
    +          if(!boardOrganizations.some((org) => org.orgDisplayName == selectedOrgDisplayName)){
    +            boardOrganizations.push({
    +              "orgId": selectedOrgId,
    +              "orgDisplayName": selectedOrgDisplayName,
    +              "isActive" : true,
    +            })
    +
    +            if (selectedOrgId != "-1") {
    +              Meteor.call('setBoardOrgs', boardOrganizations, currentBoard._id);
    +            }
    +          }
    +
    +          Popup.back();
    +        },
    +      },
    +    ];
    +  },
    +}).register('addBoardOrgPopup');
     
     Template.addBoardOrgPopup.helpers({
       orgsDatas() {
    @@ -1621,72 +1653,64 @@ Template.addBoardOrgPopup.helpers({
       },
     });
     
    -Template.addBoardOrgPopup.events({
    -  'keyup input'(event, tpl) {
    -    tpl.setError('');
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.error = new ReactiveVar('');
    +    this.loading = new ReactiveVar(false);
    +    this.findOrgsOptions = new ReactiveVar({});
    +
    +    this.page = new ReactiveVar(1);
    +    this.autorun(() => {
    +      const limitOrgs = this.page.get() * Number.MAX_SAFE_INTEGER;
    +      this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => {});
    +    });
       },
    -  'change #jsBoardOrgs'() {
    -    let currentBoard = Utils.getCurrentBoard();
    -    let selectElt = document.getElementById("jsBoardOrgs");
    -    let selectedOrgId = selectElt.options[selectElt.selectedIndex].value;
    -    let selectedOrgDisplayName = selectElt.options[selectElt.selectedIndex].text;
    -    let boardOrganizations = [];
    -    if(currentBoard.orgs !== undefined){
    -      for(let i = 0; i < currentBoard.orgs.length; i++){
    -        boardOrganizations.push(currentBoard.orgs[i]);
    -      }
    -    }
     
    -    if(!boardOrganizations.some((org) => org.orgDisplayName == selectedOrgDisplayName)){
    -      boardOrganizations.push({
    -        "orgId": selectedOrgId,
    -        "orgDisplayName": selectedOrgDisplayName,
    -        "isActive" : true,
    -      })
    -
    -      if (selectedOrgId != "-1") {
    -        Meteor.call('setBoardOrgs', boardOrganizations, currentBoard._id);
    -      }
    -    }
    -
    -    Popup.back();
    +  onRendered() {
    +    this.setLoading(false);
       },
    -});
     
    -Template.removeBoardOrgPopup.onCreated(function() {
    -  this.error = new ReactiveVar('');
    -  this.loading = new ReactiveVar(false);
    -  this.findOrgsOptions = new ReactiveVar({});
    -
    -  this.page = new ReactiveVar(1);
    -  this.autorun(() => {
    -    const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER;
    -    this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {});
    -  });
    -
    -  this.findUsersOptions = new ReactiveVar({});
    -  this.userPage = new ReactiveVar(1);
    -  this.autorun(() => {
    -    const limitUsers = this.userPage.get() * Number.MAX_SAFE_INTEGER;
    -    this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => {});
    -  });
    -
    -  this.setError = function(error) {
    +  setError(error) {
         this.error.set(error);
    -  };
    +  },
     
    -  this.setLoading = function(w) {
    +  setLoading(w) {
         this.loading.set(w);
    -  };
    +  },
     
    -  this.isLoading = function() {
    +  isLoading() {
         return this.loading.get();
    -  };
    -});
    +  },
     
    -Template.removeBoardOrgPopup.onRendered(function() {
    -  this.setLoading(false);
    -});
    +  events() {
    +    return [
    +      {
    +        'keyup input'() {
    +          this.setError('');
    +        },
    +        'click #leaveBoardBtn'(){
    +          let stringOrgId = document.getElementById('hideOrgId').value;
    +          let currentBoard = Utils.getCurrentBoard();
    +          let boardOrganizations = [];
    +          if(currentBoard.orgs !== undefined){
    +            for(let i = 0; i < currentBoard.orgs.length; i++){
    +              if(currentBoard.orgs[i].orgId != stringOrgId){
    +                boardOrganizations.push(currentBoard.orgs[i]);
    +              }
    +            }
    +          }
    +
    +          Meteor.call('setBoardOrgs', boardOrganizations, currentBoard._id);
    +
    +          Popup.back();
    +        },
    +        'click #cancelLeaveBoardBtn'(){
    +          Popup.back();
    +        },
    +      },
    +    ];
    +  },
    +}).register('removeBoardOrgPopup');
     
     Template.removeBoardOrgPopup.helpers({
       org() {
    @@ -1694,65 +1718,106 @@ Template.removeBoardOrgPopup.helpers({
       },
     });
     
    -Template.removeBoardOrgPopup.events({
    -  'keyup input'(event, tpl) {
    -    tpl.setError('');
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.error = new ReactiveVar('');
    +    this.loading = new ReactiveVar(false);
    +    this.findOrgsOptions = new ReactiveVar({});
    +
    +    this.page = new ReactiveVar(1);
    +    this.autorun(() => {
    +      const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER;
    +      this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {});
    +    });
    +
    +    this.findUsersOptions = new ReactiveVar({});
    +    this.userPage = new ReactiveVar(1);
    +    this.autorun(() => {
    +      const limitUsers = this.userPage.get() * Number.MAX_SAFE_INTEGER;
    +      this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => {});
    +    });
       },
    -  'click #leaveBoardBtn'(){
    -    let stringOrgId = document.getElementById('hideOrgId').value;
    -    let currentBoard = Utils.getCurrentBoard();
    -    let boardOrganizations = [];
    -    if(currentBoard.orgs !== undefined){
    -      for(let i = 0; i < currentBoard.orgs.length; i++){
    -        if(currentBoard.orgs[i].orgId != stringOrgId){
    -          boardOrganizations.push(currentBoard.orgs[i]);
    -        }
    -      }
    -    }
     
    -    Meteor.call('setBoardOrgs', boardOrganizations, currentBoard._id);
    -
    -    Popup.back();
    +  onRendered() {
    +    this.setLoading(false);
       },
    -  'click #cancelLeaveBoardBtn'(){
    -    Popup.back();
    -  },
    -});
     
    -Template.addBoardTeamPopup.onCreated(function() {
    -  this.error = new ReactiveVar('');
    -  this.loading = new ReactiveVar(false);
    -  this.findOrgsOptions = new ReactiveVar({});
    -
    -  this.page = new ReactiveVar(1);
    -  this.autorun(() => {
    -    const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER;
    -    this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {});
    -  });
    -
    -  this.findUsersOptions = new ReactiveVar({});
    -  this.userPage = new ReactiveVar(1);
    -  this.autorun(() => {
    -    const limitUsers = this.userPage.get() * Number.MAX_SAFE_INTEGER;
    -    this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => {});
    -  });
    -
    -  this.setError = function(error) {
    +  setError(error) {
         this.error.set(error);
    -  };
    +  },
     
    -  this.setLoading = function(w) {
    +  setLoading(w) {
         this.loading.set(w);
    -  };
    +  },
     
    -  this.isLoading = function() {
    +  isLoading() {
         return this.loading.get();
    -  };
    -});
    +  },
     
    -Template.addBoardTeamPopup.onRendered(function() {
    -  this.setLoading(false);
    -});
    +  events() {
    +    return [
    +      {
    +        'keyup input'() {
    +          this.setError('');
    +        },
    +        'change #jsBoardTeams'() {
    +          let currentBoard = Utils.getCurrentBoard();
    +          let selectElt = document.getElementById("jsBoardTeams");
    +          let selectedTeamId = selectElt.options[selectElt.selectedIndex].value;
    +          let selectedTeamDisplayName = selectElt.options[selectElt.selectedIndex].text;
    +          let boardTeams = [];
    +          if(currentBoard.teams !== undefined){
    +            for(let i = 0; i < currentBoard.teams.length; i++){
    +              boardTeams.push(currentBoard.teams[i]);
    +            }
    +          }
    +
    +          if(!boardTeams.some((team) => team.teamDisplayName == selectedTeamDisplayName)){
    +            boardTeams.push({
    +              "teamId": selectedTeamId,
    +              "teamDisplayName": selectedTeamDisplayName,
    +              "isActive" : true,
    +            })
    +
    +            if (selectedTeamId != "-1") {
    +              let members = currentBoard.members;
    +
    +              let query = {
    +                "teams.teamId": { $in: boardTeams.map(t => t.teamId) },
    +              };
    +
    +              const boardTeamUsers = ReactiveCache.getUsers(query, {
    +                sort: { sort: 1 },
    +              });
    +
    +              if(boardTeams !== undefined && boardTeams.length > 0){
    +                let index;
    +                if (boardTeamUsers && boardTeamUsers.length > 0) {
    +                  boardTeamUsers.forEach((u) => {
    +                    index = members.findIndex(function(m){ return m.userId == u._id});
    +                    if(index == -1){
    +                      members.push({
    +                        "isActive": true,
    +                        "isAdmin": u.isAdmin !== undefined ? u.isAdmin : false,
    +                        "isCommentOnly" : false,
    +                        "isNoComments" : false,
    +                        "userId": u._id,
    +                      });
    +                    }
    +                  });
    +                }
    +              }
    +
    +              Meteor.call('setBoardTeams', boardTeams, members, currentBoard._id);
    +            }
    +          }
    +
    +          Popup.back();
    +        },
    +      },
    +    ];
    +  },
    +}).register('addBoardTeamPopup');
     
     Template.addBoardTeamPopup.helpers({
       teamsDatas() {
    @@ -1761,100 +1826,92 @@ Template.addBoardTeamPopup.helpers({
       },
     });
     
    -Template.addBoardTeamPopup.events({
    -  'keyup input'(event, tpl) {
    -    tpl.setError('');
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.error = new ReactiveVar('');
    +    this.loading = new ReactiveVar(false);
    +    this.findOrgsOptions = new ReactiveVar({});
    +
    +    this.page = new ReactiveVar(1);
    +    this.autorun(() => {
    +      const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER;
    +      this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {});
    +    });
    +
    +    this.findUsersOptions = new ReactiveVar({});
    +    this.userPage = new ReactiveVar(1);
    +    this.autorun(() => {
    +      const limitUsers = this.userPage.get() * Number.MAX_SAFE_INTEGER;
    +      this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => {});
    +    });
       },
    -  'change #jsBoardTeams'() {
    -    let currentBoard = Utils.getCurrentBoard();
    -    let selectElt = document.getElementById("jsBoardTeams");
    -    let selectedTeamId = selectElt.options[selectElt.selectedIndex].value;
    -    let selectedTeamDisplayName = selectElt.options[selectElt.selectedIndex].text;
    -    let boardTeams = [];
    -    if(currentBoard.teams !== undefined){
    -      for(let i = 0; i < currentBoard.teams.length; i++){
    -        boardTeams.push(currentBoard.teams[i]);
    -      }
    -    }
     
    -    if(!boardTeams.some((team) => team.teamDisplayName == selectedTeamDisplayName)){
    -      boardTeams.push({
    -        "teamId": selectedTeamId,
    -        "teamDisplayName": selectedTeamDisplayName,
    -        "isActive" : true,
    -      })
    -
    -      if (selectedTeamId != "-1") {
    -        let members = currentBoard.members;
    -
    -        let query = {
    -          "teams.teamId": { $in: boardTeams.map(t => t.teamId) },
    -        };
    -
    -        const boardTeamUsers = ReactiveCache.getUsers(query, {
    -          sort: { sort: 1 },
    -        });
    -
    -        if(boardTeams !== undefined && boardTeams.length > 0){
    -          let index;
    -          if (boardTeamUsers && boardTeamUsers.length > 0) {
    -            boardTeamUsers.forEach((u) => {
    -              index = members.findIndex(function(m){ return m.userId == u._id});
    -              if(index == -1){
    -                members.push({
    -                  "isActive": true,
    -                  "isAdmin": u.isAdmin !== undefined ? u.isAdmin : false,
    -                  "isCommentOnly" : false,
    -                  "isNoComments" : false,
    -                  "userId": u._id,
    -                });
    -              }
    -            });
    -          }
    -        }
    -
    -        Meteor.call('setBoardTeams', boardTeams, members, currentBoard._id);
    -      }
    -    }
    -
    -    Popup.back();
    +  onRendered() {
    +    this.setLoading(false);
       },
    -});
     
    -Template.removeBoardTeamPopup.onCreated(function() {
    -  this.error = new ReactiveVar('');
    -  this.loading = new ReactiveVar(false);
    -  this.findOrgsOptions = new ReactiveVar({});
    -
    -  this.page = new ReactiveVar(1);
    -  this.autorun(() => {
    -    const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER;
    -    this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {});
    -  });
    -
    -  this.findUsersOptions = new ReactiveVar({});
    -  this.userPage = new ReactiveVar(1);
    -  this.autorun(() => {
    -    const limitUsers = this.userPage.get() * Number.MAX_SAFE_INTEGER;
    -    this.subscribe('people', this.findUsersOptions.get(), limitUsers, () => {});
    -  });
    -
    -  this.setError = function(error) {
    +  setError(error) {
         this.error.set(error);
    -  };
    +  },
     
    -  this.setLoading = function(w) {
    +  setLoading(w) {
         this.loading.set(w);
    -  };
    +  },
     
    -  this.isLoading = function() {
    +  isLoading() {
         return this.loading.get();
    -  };
    -});
    +  },
     
    -Template.removeBoardTeamPopup.onRendered(function() {
    -  this.setLoading(false);
    -});
    +  events() {
    +    return [
    +      {
    +        'keyup input'() {
    +          this.setError('');
    +        },
    +        'click #leaveBoardTeamBtn'(){
    +          let stringTeamId = document.getElementById('hideTeamId').value;
    +          let currentBoard = Utils.getCurrentBoard();
    +          let boardTeams = [];
    +          if(currentBoard.teams !== undefined){
    +            for(let i = 0; i < currentBoard.teams.length; i++){
    +              if(currentBoard.teams[i].teamId != stringTeamId){
    +                boardTeams.push(currentBoard.teams[i]);
    +              }
    +            }
    +          }
    +
    +          let members = currentBoard.members;
    +          let query = {
    +            "teams.teamId": stringTeamId
    +          };
    +
    +          const boardTeamUsers = ReactiveCache.getUsers(query, {
    +            sort: { sort: 1 },
    +          });
    +
    +          if(currentBoard.teams !== undefined && currentBoard.teams.length > 0){
    +            let index;
    +            if (boardTeamUsers && boardTeamUsers.length > 0) {
    +              boardTeamUsers.forEach((u) => {
    +                index = members.findIndex(function(m){ return m.userId == u._id});
    +                if(index !== -1 && (u.isAdmin === undefined || u.isAdmin == false)){
    +                  members.splice(index, 1);
    +                }
    +              });
    +            }
    +          }
    +
    +          Meteor.call('setBoardTeams', boardTeams, members, currentBoard._id);
    +
    +          Popup.back();
    +        },
    +        'click #cancelLeaveBoardTeamBtn'(){
    +          Popup.back();
    +        },
    +      },
    +    ];
    +  },
    +}).register('removeBoardTeamPopup');
     
     Template.removeBoardTeamPopup.helpers({
       team() {
    @@ -1862,54 +1919,8 @@ Template.removeBoardTeamPopup.helpers({
       },
     });
     
    -Template.removeBoardTeamPopup.events({
    -  'keyup input'(event, tpl) {
    -    tpl.setError('');
    -  },
    -  'click #leaveBoardTeamBtn'(){
    -    let stringTeamId = document.getElementById('hideTeamId').value;
    -    let currentBoard = Utils.getCurrentBoard();
    -    let boardTeams = [];
    -    if(currentBoard.teams !== undefined){
    -      for(let i = 0; i < currentBoard.teams.length; i++){
    -        if(currentBoard.teams[i].teamId != stringTeamId){
    -          boardTeams.push(currentBoard.teams[i]);
    -        }
    -      }
    -    }
    -
    -    let members = currentBoard.members;
    -    let query = {
    -      "teams.teamId": stringTeamId
    -    };
    -
    -    const boardTeamUsers = ReactiveCache.getUsers(query, {
    -      sort: { sort: 1 },
    -    });
    -
    -    if(currentBoard.teams !== undefined && currentBoard.teams.length > 0){
    -      let index;
    -      if (boardTeamUsers && boardTeamUsers.length > 0) {
    -        boardTeamUsers.forEach((u) => {
    -          index = members.findIndex(function(m){ return m.userId == u._id});
    -          if(index !== -1 && (u.isAdmin === undefined || u.isAdmin == false)){
    -            members.splice(index, 1);
    -          }
    -        });
    -      }
    -    }
    -
    -    Meteor.call('setBoardTeams', boardTeams, members, currentBoard._id);
    -
    -    Popup.back();
    -  },
    -  'click #cancelLeaveBoardTeamBtn'(){
    -    Popup.back();
    -  },
    -});
    -
     Template.changePermissionsPopup.events({
    -  async 'click .js-set-admin, click .js-set-normal, click .js-set-normal-assigned-only, click .js-set-no-comments, click .js-set-comment-only, click .js-set-comment-assigned-only, click .js-set-read-only, click .js-set-read-assigned-only, click .js-set-worker'(
    +  'click .js-set-admin, click .js-set-normal, click .js-set-no-comments, click .js-set-comment-only, click .js-set-worker'(
         event,
       ) {
         const currentBoard = Utils.getCurrentBoard();
    @@ -1918,26 +1929,14 @@ Template.changePermissionsPopup.events({
         const isCommentOnly = $(event.currentTarget).hasClass(
           'js-set-comment-only',
         );
    -    const isNormalAssignedOnly = $(event.currentTarget).hasClass(
    -      'js-set-normal-assigned-only',
    -    );
    -    const isCommentAssignedOnly = $(event.currentTarget).hasClass(
    -      'js-set-comment-assigned-only',
    -    );
    -    const isReadOnly = $(event.currentTarget).hasClass('js-set-read-only');
    -    const isReadAssignedOnly = $(event.currentTarget).hasClass('js-set-read-assigned-only');
         const isNoComments = $(event.currentTarget).hasClass('js-set-no-comments');
         const isWorker = $(event.currentTarget).hasClass('js-set-worker');
    -    await currentBoard.setMemberPermission(
    +    currentBoard.setMemberPermission(
           memberId,
           isAdmin,
           isNoComments,
           isCommentOnly,
           isWorker,
    -      isNormalAssignedOnly,
    -      isCommentAssignedOnly,
    -      isReadOnly,
    -      isReadAssignedOnly,
         );
         Popup.back(1);
       },
    @@ -1955,22 +1954,10 @@ Template.changePermissionsPopup.helpers({
           !currentBoard.hasAdmin(this.userId) &&
           !currentBoard.hasNoComments(this.userId) &&
           !currentBoard.hasCommentOnly(this.userId) &&
    -      !currentBoard.hasNormalAssignedOnly(this.userId) &&
    -      !currentBoard.hasCommentAssignedOnly(this.userId) &&
    -      !currentBoard.hasReadOnly(this.userId) &&
    -      !currentBoard.hasReadAssignedOnly(this.userId) &&
           !currentBoard.hasWorker(this.userId)
         );
       },
     
    -  isNormalAssignedOnly() {
    -    const currentBoard = Utils.getCurrentBoard();
    -    return (
    -      !currentBoard.hasAdmin(this.userId) &&
    -      currentBoard.hasNormalAssignedOnly(this.userId)
    -    );
    -  },
    -
       isNoComments() {
         const currentBoard = Utils.getCurrentBoard();
         return (
    @@ -1987,30 +1974,6 @@ Template.changePermissionsPopup.helpers({
         );
       },
     
    -  isCommentAssignedOnly() {
    -    const currentBoard = Utils.getCurrentBoard();
    -    return (
    -      !currentBoard.hasAdmin(this.userId) &&
    -      currentBoard.hasCommentAssignedOnly(this.userId)
    -    );
    -  },
    -
    -  isReadOnly() {
    -    const currentBoard = Utils.getCurrentBoard();
    -    return (
    -      !currentBoard.hasAdmin(this.userId) &&
    -      currentBoard.hasReadOnly(this.userId)
    -    );
    -  },
    -
    -  isReadAssignedOnly() {
    -    const currentBoard = Utils.getCurrentBoard();
    -    return (
    -      !currentBoard.hasAdmin(this.userId) &&
    -      currentBoard.hasReadAssignedOnly(this.userId)
    -    );
    -  },
    -
       isWorker() {
         const currentBoard = Utils.getCurrentBoard();
         return (
    @@ -2025,3 +1988,4 @@ Template.changePermissionsPopup.helpers({
         );
       },
     });
    +
    diff --git a/client/components/sidebar/sidebarArchives.jade b/client/components/sidebar/sidebarArchives.jade
    index 287533e09..66d1cde6c 100644
    --- a/client/components/sidebar/sidebarArchives.jade
    +++ b/client/components/sidebar/sidebarArchives.jade
    @@ -3,29 +3,26 @@ template(name="archivesSidebar")
         +basicTabs(tabs=tabs)
           +tabContent(slug="cards")
             unless isWorker
    -          unless currentUser.isReadOnly
    -            unless currentUser.isReadAssignedOnly
    -              p.quiet
    -                a.js-restore-all-cards {{_ 'restore-all'}}
    -                if currentUser.isBoardAdmin
    -                  | -
    -                  a.js-delete-all-cards {{_ 'delete-all'}}
    +          p.quiet
    +            a.js-restore-all-cards {{_ 'restore-all'}}
    +            if currentUser.isBoardAdmin
    +              | -
    +              a.js-delete-all-cards {{_ 'delete-all'}}
             each archivedCards
               .minicard-wrapper.js-minicard
                 +minicard(this)
               if currentUser.isBoardMember
                 unless isWorker
    -              unless currentUser.isReadOnly
    -                unless currentUser.isReadAssignedOnly
    -                  p.quiet
    -                    if this.archivedAt
    -                      | {{_ 'archived-at' }}
    -                      |  | {{ displayDate this.archivedAt 'LLL' }}
    -                      br
    -                    a.js-restore-card {{_ 'restore'}}
    -                    if currentUser.isBoardAdmin
    -                      | -
    -                      a.js-delete-card {{_ 'delete'}}
    +              p.quiet
    +                if this.archivedAt
    +                  | {{_ 'archived-at' }}
    +                  | 
    +                  | {{ moment this.archivedAt 'LLL' }}
    +                  br
    +                a.js-restore-card {{_ 'restore'}}
    +                if currentUser.isBoardAdmin
    +                  | -
    +                  a.js-delete-card {{_ 'delete'}}
                 if cardIsInArchivedList
                   p.quiet.small ({{_ 'warn-list-archived'}})
             else
    @@ -33,59 +30,53 @@ template(name="archivesSidebar")
     
           +tabContent(slug="lists")
             unless isWorker
    -          unless currentUser.isReadOnly
    -            unless currentUser.isReadAssignedOnly
    -              p.quiet
    -                a.js-restore-all-lists {{_ 'restore-all'}}
    -                if currentUser.isBoardAdmin
    -                  | -
    -                  a.js-delete-all-lists {{_ 'delete-all'}}
    +          p.quiet
    +            a.js-restore-all-lists {{_ 'restore-all'}}
    +            if currentUser.isBoardAdmin
    +              | -
    +              a.js-delete-all-lists {{_ 'delete-all'}}
             ul.archived-lists
               each archivedLists
                 li.archived-lists-item
                   = title
                   if currentUser.isBoardMember
                     unless isWorker
    -                  unless currentUser.isReadOnly
    -                    unless currentUser.isReadAssignedOnly
    -                      p.quiet
    -                        if this.archivedAt
    -                          | {{_ 'archived-at' }}
    -                          |  | {{ displayDate this.archivedAt 'LLL' }}
    -                          br
    -                        a.js-restore-list {{_ 'restore'}}
    -                        if currentUser.isBoardAdmin
    -                          | -
    -                          a.js-delete-list {{_ 'delete'}}
    +                  p.quiet
    +                    if this.archivedAt
    +                      | {{_ 'archived-at' }}
    +                      | 
    +                      | {{ moment this.archivedAt 'LLL' }}
    +                      br
    +                    a.js-restore-list {{_ 'restore'}}
    +                    if currentUser.isBoardAdmin
    +                      | -
    +                      a.js-delete-list {{_ 'delete'}}
               else
                 li.no-items-message {{_ 'no-archived-lists'}}
     
           +tabContent(slug="swimlanes")
             unless isWorker
    -          unless currentUser.isReadOnly
    -            unless currentUser.isReadAssignedOnly
    -              p.quiet
    -                a.js-restore-all-swimlanes {{_ 'restore-all'}}
    -                if currentUser.isBoardAdmin
    -                  | -
    -                  a.js-delete-all-swimlanes {{_ 'delete-all'}}
    +          p.quiet
    +            a.js-restore-all-swimlanes {{_ 'restore-all'}}
    +            if currentUser.isBoardAdmin
    +              | -
    +              a.js-delete-all-swimlanes {{_ 'delete-all'}}
             ul.archived-lists
               each archivedSwimlanes
                 li.archived-lists-item
                   = title
                   if currentUser.isBoardMember
                     unless isWorker
    -                  unless currentUser.isReadOnly
    -                    unless currentUser.isReadAssignedOnly
    -                      p.quiet
    -                        if this.archivedAt
    -                          | {{_ 'archived-at' }}
    -                          |  | {{ displayDate this.archivedAt 'LLL' }}
    -                          br
    -                        a.js-restore-swimlane {{_ 'restore'}}
    -                        if currentUser.isBoardAdmin
    -                          | -
    -                          a.js-delete-swimlane {{_ 'delete'}}
    +                  p.quiet
    +                    if this.archivedAt
    +                      | {{_ 'archived-at' }}
    +                      | 
    +                      | {{ moment this.archivedAt 'LLL' }}
    +                      br
    +                    a.js-restore-swimlane {{_ 'restore'}}
    +                    if currentUser.isBoardAdmin
    +                      | -
    +                      a.js-delete-swimlane {{_ 'delete'}}
               else
                 li.no-items-message {{_ 'no-archived-swimlanes'}}
       else
    diff --git a/client/components/sidebar/sidebarArchives.js b/client/components/sidebar/sidebarArchives.js
    index bc57d9a5c..f79e601ee 100644
    --- a/client/components/sidebar/sidebarArchives.js
    +++ b/client/components/sidebar/sidebarArchives.js
    @@ -1,84 +1,28 @@
     import { ReactiveCache } from '/imports/reactiveCache';
     import { TAPi18n } from '/imports/i18n';
    -import Lists from '/models/lists';
    -import Swimlanes from '/models/swimlanes';
     
     //archivedRequested = false;
     const subManager = new SubsManager();
     
    -function getArchivedCards() {
    -  const ret = ReactiveCache.getCards(
    -    {
    -      archived: true,
    -      boardId: Session.get('currentBoard'),
    -    },
    -    {
    -      sort: { archivedAt: -1, modifiedAt: -1 },
    -    },
    -  );
    -  return ret;
    -}
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.isArchiveReady = new ReactiveVar(false);
     
    -function getArchivedLists() {
    -  return ReactiveCache.getLists(
    -    {
    -      archived: true,
    -      boardId: Session.get('currentBoard'),
    -    },
    -    {
    -      sort: { archivedAt: -1, modifiedAt: -1 },
    -    },
    -  );
    -}
    -
    -function getArchivedSwimlanes() {
    -  return ReactiveCache.getSwimlanes(
    -    {
    -      archived: true,
    -      boardId: Session.get('currentBoard'),
    -    },
    -    {
    -      sort: { archivedAt: -1, modifiedAt: -1 },
    -    },
    -  );
    -}
    -
    -Template.archivesSidebar.onCreated(function () {
    -  this.isArchiveReady = new ReactiveVar(false);
    -
    -  // The pattern we use to manually handle data loading is described here:
    -  // https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management/using-subs-manager
    -  // XXX The boardId should be readed from some sort the component "props",
    -  // unfortunatly, Blaze doesn't have this notion.
    -  this.autorun(() => {
    -    const currentBoardId = Session.get('currentBoard');
    -    if (!currentBoardId) return;
    -    const handle = subManager.subscribe('board', currentBoardId, true);
    -    //archivedRequested = true;
    -    Tracker.nonreactive(() => {
    -      Tracker.autorun(() => {
    -        this.isArchiveReady.set(handle.ready());
    +    // The pattern we use to manually handle data loading is described here:
    +    // https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management/using-subs-manager
    +    // XXX The boardId should be readed from some sort the component "props",
    +    // unfortunatly, Blaze doesn't have this notion.
    +    this.autorun(() => {
    +      const currentBoardId = Session.get('currentBoard');
    +      if (!currentBoardId) return;
    +      const handle = subManager.subscribe('board', currentBoardId, true);
    +      //archivedRequested = true;
    +      Tracker.nonreactive(() => {
    +        Tracker.autorun(() => {
    +          this.isArchiveReady.set(handle.ready());
    +        });
           });
         });
    -  });
    -});
    -
    -Template.archivesSidebar.onRendered(function () {
    -  // XXX We should support dragging a card from the sidebar to the board
    -});
    -
    -Template.archivesSidebar.helpers({
    -  isArchiveReady() {
    -    return Template.instance().isArchiveReady;
    -  },
    -  isBoardAdmin() {
    -    return ReactiveCache.getCurrentUser().isBoardAdmin();
    -  },
    -  isWorker() {
    -    const currentBoard = Utils.getCurrentBoard();
    -    return (
    -      !currentBoard.hasAdmin(this.userId) && currentBoard.hasWorker(this.userId)
    -    );
       },
     
       tabs() {
    @@ -90,98 +34,139 @@ Template.archivesSidebar.helpers({
       },
     
       archivedCards() {
    -    return getArchivedCards();
    +    const ret = ReactiveCache.getCards(
    +      {
    +        archived: true,
    +        boardId: Session.get('currentBoard'),
    +      },
    +      {
    +        sort: { archivedAt: -1, modifiedAt: -1 },
    +      },
    +    );
    +    return ret;
       },
     
       archivedLists() {
    -    return getArchivedLists();
    +    return ReactiveCache.getLists(
    +      {
    +        archived: true,
    +        boardId: Session.get('currentBoard'),
    +      },
    +      {
    +        sort: { archivedAt: -1, modifiedAt: -1 },
    +      },
    +    );
       },
     
       archivedSwimlanes() {
    -    return getArchivedSwimlanes();
    +    return ReactiveCache.getSwimlanes(
    +      {
    +        archived: true,
    +        boardId: Session.get('currentBoard'),
    +      },
    +      {
    +        sort: { archivedAt: -1, modifiedAt: -1 },
    +      },
    +    );
       },
     
       cardIsInArchivedList() {
    -    return Template.currentData().list().archived;
    +    return this.currentData().list().archived;
    +  },
    +
    +  onRendered() {
    +    // XXX We should support dragging a card from the sidebar to the board
    +  },
    +
    +  events() {
    +    return [
    +      {
    +        'click .js-restore-card'() {
    +          const card = this.currentData();
    +          if (card.canBeRestored()) {
    +            card.restore();
    +          }
    +        },
    +        'click .js-restore-all-cards'() {
    +          this.archivedCards().forEach(card => {
    +            if (card.canBeRestored()) {
    +              card.restore();
    +            }
    +          });
    +        },
    +
    +        'click .js-delete-card': Popup.afterConfirm('cardDelete', function() {
    +          const cardId = this._id;
    +          Cards.remove(cardId);
    +          Popup.back();
    +        }),
    +        'click .js-delete-all-cards': Popup.afterConfirm('cardDelete', () => {
    +          this.archivedCards().forEach(card => {
    +            Cards.remove(card._id);
    +          });
    +          Popup.back();
    +        }),
    +
    +        'click .js-restore-list'() {
    +          const list = this.currentData();
    +          list.restore();
    +        },
    +        'click .js-restore-all-lists'() {
    +          this.archivedLists().forEach(list => {
    +            list.restore();
    +          });
    +        },
    +
    +        'click .js-delete-list': Popup.afterConfirm('listDelete', function() {
    +          this.remove();
    +          Popup.back();
    +        }),
    +        'click .js-delete-all-lists': Popup.afterConfirm('listDelete', () => {
    +          this.archivedLists().forEach(list => {
    +            list.remove();
    +          });
    +          Popup.back();
    +        }),
    +
    +        'click .js-restore-swimlane'() {
    +          const swimlane = this.currentData();
    +          swimlane.restore();
    +        },
    +        'click .js-restore-all-swimlanes'() {
    +          this.archivedSwimlanes().forEach(swimlane => {
    +            swimlane.restore();
    +          });
    +        },
    +
    +        'click .js-delete-swimlane': Popup.afterConfirm(
    +          'swimlaneDelete',
    +          function() {
    +            this.remove();
    +            Popup.back();
    +          },
    +        ),
    +        'click .js-delete-all-swimlanes': Popup.afterConfirm(
    +          'swimlaneDelete',
    +          () => {
    +            this.archivedSwimlanes().forEach(swimlane => {
    +              swimlane.remove();
    +            });
    +            Popup.back();
    +          },
    +        ),
    +      },
    +    ];
    +  },
    +}).register('archivesSidebar');
    +
    +Template.archivesSidebar.helpers({
    +  isBoardAdmin() {
    +    return ReactiveCache.getCurrentUser().isBoardAdmin();
    +  },
    +  isWorker() {
    +    const currentBoard = Utils.getCurrentBoard();
    +    return (
    +      !currentBoard.hasAdmin(this.userId) && currentBoard.hasWorker(this.userId)
    +    );
       },
     });
    -
    -Template.archivesSidebar.events({
    -  async 'click .js-restore-card'() {
    -    const card = Template.currentData();
    -    if (card.canBeRestored()) {
    -      await card.restore();
    -    }
    -  },
    -  async 'click .js-restore-all-cards'() {
    -    for (const card of getArchivedCards()) {
    -      if (card.canBeRestored()) {
    -        await card.restore();
    -      }
    -    }
    -  },
    -
    -  'click .js-delete-card': Popup.afterConfirm('cardDelete', async function() {
    -    const cardId = this._id;
    -    await Cards.removeAsync(cardId);
    -    Popup.back();
    -  }),
    -  'click .js-delete-all-cards': Popup.afterConfirm('cardDelete', async () => {
    -    for (const card of getArchivedCards()) {
    -      await Cards.removeAsync(card._id);
    -    }
    -    Popup.back();
    -  }),
    -
    -  async 'click .js-restore-list'() {
    -    const data = Template.currentData();
    -    const list = Lists.findOne(data._id) || data;
    -    await list.restore();
    -  },
    -  async 'click .js-restore-all-lists'() {
    -    for (const list of getArchivedLists()) {
    -      await list.restore();
    -    }
    -  },
    -
    -  'click .js-delete-list': Popup.afterConfirm('listDelete', async function() {
    -    const list = Lists.findOne(this._id);
    -    if (list) await list.remove();
    -    Popup.back();
    -  }),
    -  'click .js-delete-all-lists': Popup.afterConfirm('listDelete', async () => {
    -    for (const list of getArchivedLists()) {
    -      await list.remove();
    -    }
    -    Popup.back();
    -  }),
    -
    -  async 'click .js-restore-swimlane'() {
    -    const data = Template.currentData();
    -    const swimlane = Swimlanes.findOne(data._id) || data;
    -    await swimlane.restore();
    -  },
    -  async 'click .js-restore-all-swimlanes'() {
    -    for (const swimlane of getArchivedSwimlanes()) {
    -      await swimlane.restore();
    -    }
    -  },
    -
    -  'click .js-delete-swimlane': Popup.afterConfirm(
    -    'swimlaneDelete',
    -    async function() {
    -      const swimlane = Swimlanes.findOne(this._id);
    -      if (swimlane) await swimlane.remove();
    -      Popup.back();
    -    },
    -  ),
    -  'click .js-delete-all-swimlanes': Popup.afterConfirm(
    -    'swimlaneDelete',
    -    async () => {
    -      for (const swimlane of getArchivedSwimlanes()) {
    -        await swimlane.remove();
    -      }
    -      Popup.back();
    -    },
    -  ),
    -});
    diff --git a/client/components/sidebar/sidebarCustomFields.jade b/client/components/sidebar/sidebarCustomFields.jade
    index d8df8e157..1cc270681 100644
    --- a/client/components/sidebar/sidebarCustomFields.jade
    +++ b/client/components/sidebar/sidebarCustomFields.jade
    @@ -14,7 +14,7 @@ template(name="customFieldsSidebar")
         if currentUser.isBoardMember
             hr
             a.sidebar-btn.js-open-create-custom-field
    -            i.fa.fa-plus
    +            | ➕
                 span {{_ 'createCustomField'}}
     
     template(name="createCustomFieldPopup")
    @@ -95,7 +95,3 @@ template(name="createCustomFieldPopup")
     template(name="deleteCustomFieldPopup")
         p {{_ "custom-field-delete-pop"}}
         button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
    -
    -// Reuse the create form for editing to satisfy popup template lookup
    -template(name="editCustomFieldPopup")
    -    +Template.dynamic(template="createCustomFieldPopup")
    diff --git a/client/components/sidebar/sidebarCustomFields.js b/client/components/sidebar/sidebarCustomFields.js
    index 1956c9713..cae2bc28c 100644
    --- a/client/components/sidebar/sidebarCustomFields.js
    +++ b/client/components/sidebar/sidebarCustomFields.js
    @@ -1,119 +1,111 @@
     import { ReactiveCache } from '/imports/reactiveCache';
     import { TAPi18n } from '/imports/i18n';
     
    -Template.customFieldsSidebar.helpers({
    +BlazeComponent.extendComponent({
       customFields() {
         const ret = ReactiveCache.getCustomFields({
           boardIds: { $in: [Session.get('currentBoard')] },
         });
         return ret;
       },
    -});
     
    -Template.customFieldsSidebar.events({
    -  'click .js-open-create-custom-field': Popup.open('createCustomField'),
    -  'click .js-edit-custom-field': Popup.open('editCustomField'),
    -});
    +  events() {
    +    return [
    +      {
    +        'click .js-open-create-custom-field': Popup.open('createCustomField'),
    +        'click .js-edit-custom-field': Popup.open('editCustomField'),
    +      },
    +    ];
    +  },
    +}).register('customFieldsSidebar');
     
    -const CUSTOM_FIELD_TYPES = [
    -  'text',
    -  'number',
    -  'date',
    -  'dropdown',
    -  'currency',
    -  'checkbox',
    -  'stringtemplate',
    -];
    +const CreateCustomFieldPopup = BlazeComponent.extendComponent({
    +  _types: [
    +    'text',
    +    'number',
    +    'date',
    +    'dropdown',
    +    'currency',
    +    'checkbox',
    +    'stringtemplate',
    +  ],
     
    -const CURRENCY_LIST = [
    -  { name: 'US Dollar', code: 'USD' },
    -  { name: 'Euro', code: 'EUR' },
    -  { name: 'Yen', code: 'JPY' },
    -  { name: 'Pound Sterling', code: 'GBP' },
    -  { name: 'Australian Dollar', code: 'AUD' },
    -  { name: 'Canadian Dollar', code: 'CAD' },
    -  { name: 'Swiss Franc', code: 'CHF' },
    -  { name: 'Yuan Renminbi', code: 'CNY' },
    -  { name: 'Hong Kong Dollar', code: 'HKD' },
    -  { name: 'New Zealand Dollar', code: 'NZD' },
    -];
    -
    -function getDropdownItems(tpl) {
    -  const items = tpl.dropdownItems.get();
    -  Array.from(tpl.findAll('.js-field-settings-dropdown input')).forEach(
    -    (el, index) => {
    -      if (!items[index])
    -        items[index] = {
    -          _id: Random.id(6),
    -        };
    -      items[index].name = el.value.trim();
    +  _currencyList: [
    +    {
    +      name: 'US Dollar',
    +      code: 'USD',
         },
    -  );
    -  return items;
    -}
    +    {
    +      name: 'Euro',
    +      code: 'EUR',
    +    },
    +    {
    +      name: 'Yen',
    +      code: 'JPY',
    +    },
    +    {
    +      name: 'Pound Sterling',
    +      code: 'GBP',
    +    },
    +    {
    +      name: 'Australian Dollar',
    +      code: 'AUD',
    +    },
    +    {
    +      name: 'Canadian Dollar',
    +      code: 'CAD',
    +    },
    +    {
    +      name: 'Swiss Franc',
    +      code: 'CHF',
    +    },
    +    {
    +      name: 'Yuan Renminbi',
    +      code: 'CNY',
    +    },
    +    {
    +      name: 'Hong Kong Dollar',
    +      code: 'HKD',
    +    },
    +    {
    +      name: 'New Zealand Dollar',
    +      code: 'NZD',
    +    },
    +  ],
     
    -function getSettings(tpl) {
    -  const settings = {};
    -  switch (tpl.type.get()) {
    -    case 'currency': {
    -      const currencyCode = tpl.currencyCode.get();
    -      settings.currencyCode = currencyCode;
    -      break;
    -    }
    -    case 'dropdown': {
    -      const dropdownItems = getDropdownItems(tpl).filter(
    -        item => !!item.name.trim(),
    -      );
    -      settings.dropdownItems = dropdownItems;
    -      break;
    -    }
    -    case 'stringtemplate': {
    -      const stringtemplateFormat = tpl.stringtemplateFormat.get();
    -      settings.stringtemplateFormat = stringtemplateFormat;
    +  onCreated() {
    +    this.type = new ReactiveVar(
    +      this.data().type ? this.data().type : this._types[0],
    +    );
     
    -      const stringtemplateSeparator = tpl.stringtemplateSeparator.get();
    -      settings.stringtemplateSeparator = stringtemplateSeparator;
    -      break;
    -    }
    -  }
    -  return settings;
    -}
    +    this.currencyCode = new ReactiveVar(
    +      this.data().settings && this.data().settings.currencyCode
    +        ? this.data().settings.currencyCode
    +        : this._currencyList[0].code,
    +    );
     
    -Template.createCustomFieldPopup.onCreated(function () {
    -  const data = Template.currentData();
    -  this.type = new ReactiveVar(
    -    data.type ? data.type : CUSTOM_FIELD_TYPES[0],
    -  );
    +    this.dropdownItems = new ReactiveVar(
    +      this.data().settings && this.data().settings.dropdownItems
    +        ? this.data().settings.dropdownItems
    +        : [],
    +    );
     
    -  this.currencyCode = new ReactiveVar(
    -    data.settings && data.settings.currencyCode
    -      ? data.settings.currencyCode
    -      : CURRENCY_LIST[0].code,
    -  );
    +    this.stringtemplateFormat = new ReactiveVar(
    +      this.data().settings && this.data().settings.stringtemplateFormat
    +        ? this.data().settings.stringtemplateFormat
    +        : '',
    +    );
     
    -  this.dropdownItems = new ReactiveVar(
    -    data.settings && data.settings.dropdownItems
    -      ? data.settings.dropdownItems
    -      : [],
    -  );
    +    this.stringtemplateSeparator = new ReactiveVar(
    +      this.data().settings && this.data().settings.stringtemplateSeparator
    +        ? this.data().settings.stringtemplateSeparator
    +        : '',
    +    );
    +  },
     
    -  this.stringtemplateFormat = new ReactiveVar(
    -    data.settings && data.settings.stringtemplateFormat
    -      ? data.settings.stringtemplateFormat
    -      : '',
    -  );
    -
    -  this.stringtemplateSeparator = new ReactiveVar(
    -    data.settings && data.settings.stringtemplateSeparator
    -      ? data.settings.stringtemplateSeparator
    -      : '',
    -  );
    -});
    -
    -Template.createCustomFieldPopup.helpers({
       types() {
    -    const currentType = Template.currentData().type;
    -    return CUSTOM_FIELD_TYPES.map(type => {
    +    const currentType = this.data().type;
    +    return this._types.map(type => {
           return {
             value: type,
             name: TAPi18n.__(`custom-field-${type}`),
    @@ -123,13 +115,13 @@ Template.createCustomFieldPopup.helpers({
       },
     
       isTypeNotSelected(type) {
    -    return Template.instance().type.get() !== type;
    +    return this.type.get() !== type;
       },
     
       getCurrencyCodes() {
    -    const currentCode = Template.instance().currencyCode.get();
    +    const currentCode = this.currencyCode.get();
     
    -    return CURRENCY_LIST.map(({ name, code }) => {
    +    return this._currencyList.map(({ name, code }) => {
           return {
             name: `${code} - ${name}`,
             value: code,
    @@ -139,128 +131,176 @@ Template.createCustomFieldPopup.helpers({
       },
     
       getDropdownItems() {
    -    return getDropdownItems(Template.instance());
    +    const items = this.dropdownItems.get();
    +    Array.from(this.findAll('.js-field-settings-dropdown input')).forEach(
    +      (el, index) => {
    +        //console.log('each item!', index, el.value);
    +        if (!items[index])
    +          items[index] = {
    +            _id: Random.id(6),
    +          };
    +        items[index].name = el.value.trim();
    +      },
    +    );
    +    return items;
       },
     
       getStringtemplateFormat() {
    -    return Template.instance().stringtemplateFormat.get();
    +    return this.stringtemplateFormat.get();
       },
     
       getStringtemplateSeparator() {
    -    return Template.instance().stringtemplateSeparator.get();
    +    return this.stringtemplateSeparator.get();
       },
    -});
     
    -Template.createCustomFieldPopup.events({
    -  'change .js-field-type'(evt, tpl) {
    -    const value = evt.target.value;
    -    tpl.type.set(value);
    -  },
    -  'change .js-field-currency'(evt, tpl) {
    -    const value = evt.target.value;
    -    tpl.currencyCode.set(value);
    -  },
    -  'keydown .js-dropdown-item.last'(evt, tpl) {
    -    if (evt.target.value.trim() && evt.keyCode === 13) {
    -      const items = getDropdownItems(tpl);
    -      tpl.dropdownItems.set(items);
    -      evt.target.value = '';
    -    }
    -  },
    -  'input .js-field-stringtemplate-format'(evt, tpl) {
    -    const value = evt.target.value;
    -    tpl.stringtemplateFormat.set(value);
    -  },
    -  'input .js-field-stringtemplate-separator'(evt, tpl) {
    -    const value = evt.target.value;
    -    tpl.stringtemplateSeparator.set(value);
    -  },
    -  'click .js-field-show-on-card'(evt) {
    -    let $target = $(evt.target);
    -    if (!$target.hasClass('js-field-show-on-card')) {
    -      $target = $target.parent();
    -    }
    -    $target.find('.materialCheckBox').toggleClass('is-checked');
    -    $target.toggleClass('is-checked');
    -  },
    -  'click .js-field-automatically-on-card'(evt) {
    -    let $target = $(evt.target);
    -    if (!$target.hasClass('js-field-automatically-on-card')) {
    -      $target = $target.parent();
    -    }
    -    $target.find('.materialCheckBox').toggleClass('is-checked');
    -    $target.toggleClass('is-checked');
    -  },
    -  'click .js-field-always-on-card'(evt) {
    -    let $target = $(evt.target);
    -    if (!$target.hasClass('js-field-always-on-card')) {
    -      $target = $target.parent();
    -    }
    -    $target.find('.materialCheckBox').toggleClass('is-checked');
    -    $target.toggleClass('is-checked');
    -  },
    -  'click .js-field-showLabel-on-card'(evt) {
    -    let $target = $(evt.target);
    -    if (!$target.hasClass('js-field-showLabel-on-card')) {
    -      $target = $target.parent();
    -    }
    -    $target.find('.materialCheckBox').toggleClass('is-checked');
    -    $target.toggleClass('is-checked');
    -  },
    -  'click .js-field-show-sum-at-top-of-list'(evt) {
    -    let $target = $(evt.target);
    -    if (!$target.hasClass('js-field-show-sum-at-top-of-list')) {
    -      $target = $target.parent();
    -    }
    -    $target.find('.materialCheckBox').toggleClass('is-checked');
    -    $target.toggleClass('is-checked');
    -  },
    -  'click .primary'(evt, tpl) {
    -    evt.preventDefault();
    -
    -    const data = {
    -      name: tpl.find('.js-field-name').value.trim(),
    -      type: tpl.type.get(),
    -      settings: getSettings(tpl),
    -      showOnCard: tpl.find('.js-field-show-on-card.is-checked') !== null,
    -      showLabelOnMiniCard:
    -        tpl.find('.js-field-showLabel-on-card.is-checked') !== null,
    -      automaticallyOnCard:
    -        tpl.find('.js-field-automatically-on-card.is-checked') !== null,
    -      alwaysOnCard:
    -        tpl.find('.js-field-always-on-card.is-checked') !== null,
    -      showSumAtTopOfList:
    -        tpl.find('.js-field-show-sum-at-top-of-list.is-checked') !== null,
    -    };
    -
    -    const currentData = Template.currentData();
    -    // insert or update
    -    if (!currentData._id) {
    -      data.boardIds = [Session.get('currentBoard')];
    -      CustomFields.insert(data);
    -    } else {
    -      CustomFields.update(currentData._id, { $set: data });
    -    }
    -
    -    Popup.back();
    -  },
    -  'click .js-delete-custom-field': Popup.afterConfirm(
    -    'deleteCustomField',
    -    function() {
    -      const customField = ReactiveCache.getCustomField(this._id);
    -      if (customField.boardIds.length > 1) {
    -        CustomFields.update(customField._id, {
    -          $pull: {
    -            boardIds: Session.get('currentBoard'),
    -          },
    -        });
    -      } else {
    -        CustomFields.remove(customField._id);
    +  getSettings() {
    +    const settings = {};
    +    switch (this.type.get()) {
    +      case 'currency': {
    +        const currencyCode = this.currencyCode.get();
    +        settings.currencyCode = currencyCode;
    +        break;
           }
    -      Popup.back();
    -    },
    -  ),
    +      case 'dropdown': {
    +        const dropdownItems = this.getDropdownItems().filter(
    +          item => !!item.name.trim(),
    +        );
    +        settings.dropdownItems = dropdownItems;
    +        break;
    +      }
    +      case 'stringtemplate': {
    +        const stringtemplateFormat = this.stringtemplateFormat.get();
    +        settings.stringtemplateFormat = stringtemplateFormat;
    +
    +        const stringtemplateSeparator = this.stringtemplateSeparator.get();
    +        settings.stringtemplateSeparator = stringtemplateSeparator;
    +        break;
    +      }
    +    }
    +    return settings;
    +  },
    +
    +  events() {
    +    return [
    +      {
    +        'change .js-field-type'(evt) {
    +          const value = evt.target.value;
    +          this.type.set(value);
    +        },
    +        'change .js-field-currency'(evt) {
    +          const value = evt.target.value;
    +          this.currencyCode.set(value);
    +        },
    +        'keydown .js-dropdown-item.last'(evt) {
    +          if (evt.target.value.trim() && evt.keyCode === 13) {
    +            const items = this.getDropdownItems();
    +            this.dropdownItems.set(items);
    +            evt.target.value = '';
    +          }
    +        },
    +        'input .js-field-stringtemplate-format'(evt) {
    +          const value = evt.target.value;
    +          this.stringtemplateFormat.set(value);
    +        },
    +        'input .js-field-stringtemplate-separator'(evt) {
    +          const value = evt.target.value;
    +          this.stringtemplateSeparator.set(value);
    +        },
    +        'click .js-field-show-on-card'(evt) {
    +          let $target = $(evt.target);
    +          if (!$target.hasClass('js-field-show-on-card')) {
    +            $target = $target.parent();
    +          }
    +          $target.find('.materialCheckBox').toggleClass('is-checked');
    +          $target.toggleClass('is-checked');
    +        },
    +        'click .js-field-automatically-on-card'(evt) {
    +          let $target = $(evt.target);
    +          if (!$target.hasClass('js-field-automatically-on-card')) {
    +            $target = $target.parent();
    +          }
    +          $target.find('.materialCheckBox').toggleClass('is-checked');
    +          $target.toggleClass('is-checked');
    +        },
    +        'click .js-field-always-on-card'(evt) {
    +          let $target = $(evt.target);
    +          if (!$target.hasClass('js-field-always-on-card')) {
    +            $target = $target.parent();
    +          }
    +          $target.find('.materialCheckBox').toggleClass('is-checked');
    +          $target.toggleClass('is-checked');
    +        },
    +        'click .js-field-showLabel-on-card'(evt) {
    +          let $target = $(evt.target);
    +          if (!$target.hasClass('js-field-showLabel-on-card')) {
    +            $target = $target.parent();
    +          }
    +          $target.find('.materialCheckBox').toggleClass('is-checked');
    +          $target.toggleClass('is-checked');
    +        },
    +        'click .js-field-show-sum-at-top-of-list'(evt) {
    +          let $target = $(evt.target);
    +          if (!$target.hasClass('js-field-show-sum-at-top-of-list')) {
    +            $target = $target.parent();
    +          }
    +          $target.find('.materialCheckBox').toggleClass('is-checked');
    +          $target.toggleClass('is-checked');
    +        },
    +        'click .primary'(evt) {
    +          evt.preventDefault();
    +
    +          const data = {
    +            name: this.find('.js-field-name').value.trim(),
    +            type: this.type.get(),
    +            settings: this.getSettings(),
    +            showOnCard: this.find('.js-field-show-on-card.is-checked') !== null,
    +            showLabelOnMiniCard:
    +              this.find('.js-field-showLabel-on-card.is-checked') !== null,
    +            automaticallyOnCard:
    +              this.find('.js-field-automatically-on-card.is-checked') !== null,
    +            alwaysOnCard:
    +              this.find('.js-field-always-on-card.is-checked') !== null,
    +            showSumAtTopOfList:
    +              this.find('.js-field-show-sum-at-top-of-list.is-checked') !== null,
    +          };
    +
    +          // insert or update
    +          if (!this.data()._id) {
    +            data.boardIds = [Session.get('currentBoard')];
    +            CustomFields.insert(data);
    +          } else {
    +            CustomFields.update(this.data()._id, { $set: data });
    +          }
    +
    +          Popup.back();
    +        },
    +        'click .js-delete-custom-field': Popup.afterConfirm(
    +          'deleteCustomField',
    +          function() {
    +            const customField = ReactiveCache.getCustomField(this._id);
    +            if (customField.boardIds.length > 1) {
    +              CustomFields.update(customField._id, {
    +                $pull: {
    +                  boardIds: Session.get('currentBoard'),
    +                },
    +              });
    +            } else {
    +              CustomFields.remove(customField._id);
    +            }
    +            Popup.back();
    +          },
    +        ),
    +      },
    +    ];
    +  },
     });
    +CreateCustomFieldPopup.register('createCustomFieldPopup');
    +
    +(class extends CreateCustomFieldPopup {
    +  template() {
    +    return 'createCustomFieldPopup';
    +  }
    +}.register('editCustomFieldPopup'));
     
     /*Template.deleteCustomFieldPopup.events({
       'submit'(evt) {
    diff --git a/client/components/sidebar/sidebarFilters.jade b/client/components/sidebar/sidebarFilters.jade
    index a450c959a..918711b09 100644
    --- a/client/components/sidebar/sidebarFilters.jade
    +++ b/client/components/sidebar/sidebarFilters.jade
    @@ -5,19 +5,19 @@
     
     template(name="filterSidebar")
       h3
    -    i.fa.fa-list
    +    | 📋
         | {{_ 'list-filter-label'}}
       ul.sidebar-list
         form.js-list-filter
           input(type="text")
       hr
       h3
    -    i.fa.fa-list
    +    | 📋
         | {{_ 'filter-card-title-label'}}
       input.js-field-card-filter(type="text")
       hr
       h3
    -    i.fa.fa-tag
    +    | 🏷️
         | {{_ 'filter-labels-label'}}
       ul.sidebar-list
         li(class="{{#if Filter.labelIds.isSelected undefined}}active{{/if}}")
    @@ -25,7 +25,7 @@ template(name="filterSidebar")
                 span.sidebar-list-item-description
                   | {{_ 'filter-no-label'}}
                 if Filter.labelIds.isSelected undefined
    -              i.fa.fa-check
    +              | ✅
         each currentBoard.labels
           li
             a.name.js-toggle-label-filter
    @@ -39,7 +39,7 @@ template(name="filterSidebar")
                 i.fa.fa-check
       hr
       h3
    -    i.fa.fa-users
    +    | 👥
         | {{_ 'filter-member-label'}}
       ul.sidebar-list
         li(class="{{#if Filter.members.isSelected undefined}}active{{/if}}")
    @@ -47,7 +47,7 @@ template(name="filterSidebar")
                 span.sidebar-list-item-description
                   | {{_ 'filter-no-member'}}
                 if Filter.members.isSelected undefined
    -              i.fa.fa-check
    +              | ✅
         each currentBoard.activeMembers
           with getUser userId
             li(class="{{#if Filter.members.isSelected _id}}active{{/if}}")
    @@ -57,10 +57,10 @@ template(name="filterSidebar")
                   = profile.fullname
                   | ({{ username }})
                 if Filter.members.isSelected _id
    -              i.fa.fa-check
    +              | ✅
       hr
       h3
    -    i.fa.fa-user
    +    | 👤
         | {{_ 'filter-assignee-label'}}
       ul.sidebar-list
         li(class="{{#if Filter.assignees.isSelected undefined}}active{{/if}}")
    @@ -78,10 +78,11 @@ template(name="filterSidebar")
                   = profile.fullname
                   | ({{ username }})
                 if Filter.assignees.isSelected _id
    -              i.fa.fa-check
    +              | ✅
    +
       hr
       h3
    -    i.fa.fa-calendar
    +    | 📅
         | {{_ 'filter-dates-label' }}
       ul.sidebar-list
         li(class="{{#if Filter.dueAt.isSelected 'noDate'}}active{{/if}}")
    @@ -122,7 +123,7 @@ template(name="filterSidebar")
               i.fa.fa-check
       hr
       h3
    -    i.fa.fa-list
    +    | 📋
         | {{_ 'filter-custom-fields-label'}}
       ul.sidebar-list
         li(class="{{#if Filter.customFields.isSelected undefined}}active{{/if}}")
    @@ -130,7 +131,7 @@ template(name="filterSidebar")
                 span.sidebar-list-item-description
                   | {{_ 'filter-no-custom-fields'}}
                 if Filter.customFields.isSelected undefined
    -              i.fa.fa-check
    +              | ✅
         each currentBoard.customFields
           li(class="{{#if Filter.customFields.isSelected _id}}active{{/if}}")
             a.name.js-toggle-custom-fields-filter
    @@ -162,15 +163,15 @@ template(name="filterSidebar")
       if Filter.isActive
         hr
         a.sidebar-btn.js-clear-all
    -      i.fa.fa-search
    +      | 🔍
           span {{_ 'filter-clear'}}
         a.sidebar-btn.js-filter-to-selection
    -      i.fa.fa-check
    +      | ☑️
           span {{_ 'filter-to-selection'}}
     
     template(name="multiselectionSidebar")
       h3
    -    i.fa.fa-tag
    +    | 🏷️
         | {{_ 'multi-selection-label'}}
       ul.sidebar-list
         each currentBoard.labels
    @@ -183,12 +184,12 @@ template(name="multiselectionSidebar")
                 else
                   span.quiet {{_ "label-default" (_ (concat "color-" color))}}
               if allSelectedElementHave 'label' _id
    -            i.fa.fa-check
    +            | ✅
               else if someSelectedElementHave 'label' _id
                 | ⋯
       hr
       h3
    -    i.fa.fa-users
    +    | 👥
         | {{_ 'multi-selection-member'}}
       ul.sidebar-list
         each currentBoard.activeMembers
    @@ -200,22 +201,16 @@ template(name="multiselectionSidebar")
                   = profile.fullname
                   | ({{ username }})
                 if allSelectedElementHave 'member' _id
    -              i.fa.fa-check
    +              | ✅
                 else if someSelectedElementHave 'member' _id
                   | ⋯
       if currentUser.isBoardAdmin
         hr
    -    a.sidebar-btn.js-selection-color
    -      i.fa.fa-paint-brush
    -      span {{_ 'selection-color'}}
    -    a.sidebar-btn.js-copy-selection
    -      i.fa.fa-clipboard
    -      span {{_ 'copy-selection'}}
         a.sidebar-btn.js-move-selection
    -      i.fa.fa-upload
    +      | 📤
           span {{_ 'move-selection'}}
         a.sidebar-btn.js-archive-selection
    -      i.fa.fa-archive
    +      | 📦
           span {{_ 'archive-selection'}}
     
     template(name="disambiguateMultiLabelPopup")
    @@ -229,76 +224,4 @@ template(name="disambiguateMultiMemberPopup")
       button.wide.js-assign-member {{_ 'assign-member'}}
     
     template(name="moveSelectionPopup")
    -  h3 {{_ 'moveSelectionPopup-title'}}
    -  label {{_ 'boards'}}:
    -  select.js-select-boards
    -    each boards
    -      option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
    -
    -  label {{_ 'swimlanes'}}:
    -  select.js-select-swimlanes
    -    each swimlanes
    -      option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{isTitleDefault title}}
    -
    -  label {{_ 'lists'}}:
    -  select.js-select-lists
    -    each lists
    -      option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
    -
    -  label {{_ 'cards'}}:
    -  select.js-select-cards
    -    each cards
    -      option(value="{{_id}}") {{add @index 1}}. {{title}}
    -
    -  div
    -    input(type="radio" name="position" value="above" checked id="position-above-move" style="display: inline")
    -    label(for="position-above-move") {{_ 'above-selected-card'}}
    -  div
    -    input(type="radio" name="position" value="below" id="position-below-move" style="display: inline")
    -    label(for="position-below-move") {{_ 'below-selected-card'}}
    -
    -  .edit-controls.clearfix
    -    button.primary.confirm.js-done {{_ 'done'}}
    -
    -template(name="copySelectionPopup")
    -  h3 {{_ 'copySelectionPopup-title'}}
    -  label {{_ 'boards'}}:
    -  select.js-select-boards
    -    each boards
    -      option(value="{{_id}}" selected="{{#if isDialogOptionBoardId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
    -
    -  label {{_ 'swimlanes'}}:
    -  select.js-select-swimlanes
    -    each swimlanes
    -      option(value="{{_id}}" selected="{{#if isDialogOptionSwimlaneId _id}}selected{{/if}}") {{add @index 1}}. {{isTitleDefault title}}
    -
    -  label {{_ 'lists'}}:
    -  select.js-select-lists
    -    each lists
    -      option(value="{{_id}}" selected="{{#if isDialogOptionListId _id}}selected{{/if}}") {{add @index 1}}. {{title}}
    -
    -  label {{_ 'cards'}}:
    -  select.js-select-cards
    -    each cards
    -      option(value="{{_id}}") {{add @index 1}}. {{title}}
    -
    -  div
    -    input(type="radio" name="position" value="above" checked id="position-above-copy" style="display: inline")
    -    label(for="position-above-copy") {{_ 'above-selected-card'}}
    -  div
    -    input(type="radio" name="position" value="below" id="position-below-copy" style="display: inline")
    -    label(for="position-below-copy") {{_ 'below-selected-card'}}
    -
    -  .edit-controls.clearfix
    -    button.primary.confirm.js-done {{_ 'done'}}
    -
    -template(name="setSelectionColorPopup")
    -  h3 {{_ 'setSelectionColorPopup-title'}}
    -  form.edit-label
    -    .palette-colors: each colors
    -      unless $eq color 'white'
    -        span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
    -          if(isSelected color)
    -            i.fa.fa-check
    -    button.primary.confirm.js-submit {{_ 'save'}}
    -    button.js-remove-color.negate.wide.right {{_ 'unset-color'}}
    +  +boardLists
    diff --git a/client/components/sidebar/sidebarFilters.js b/client/components/sidebar/sidebarFilters.js
    index e3b041a4f..64c6f3d8c 100644
    --- a/client/components/sidebar/sidebarFilters.js
    +++ b/client/components/sidebar/sidebarFilters.js
    @@ -1,187 +1,176 @@
     import { ReactiveCache } from '/imports/reactiveCache';
    -import { TAPi18n } from '/imports/i18n';
     
     const subManager = new SubsManager();
     
    -Template.filterSidebar.events({
    -  'submit .js-list-filter'(evt, tpl) {
    -    evt.preventDefault();
    -    Filter.lists.set(tpl.find('.js-list-filter input').value.trim());
    +BlazeComponent.extendComponent({
    +  events() {
    +    return [
    +      {
    +        'submit .js-list-filter'(evt) {
    +          evt.preventDefault();
    +          Filter.lists.set(this.find('.js-list-filter input').value.trim());
    +        },
    +        'change .js-field-card-filter'(evt) {
    +          evt.preventDefault();
    +          Filter.title.set(this.find('.js-field-card-filter').value.trim());
    +          Filter.resetExceptions();
    +        },
    +        'click .js-toggle-label-filter'(evt) {
    +          evt.preventDefault();
    +          Filter.labelIds.toggle(this.currentData()._id);
    +          Filter.resetExceptions();
    +        },
    +        'click .js-toggle-member-filter'(evt) {
    +          evt.preventDefault();
    +          Filter.members.toggle(this.currentData()._id);
    +          Filter.resetExceptions();
    +        },
    +        'click .js-toggle-assignee-filter'(evt) {
    +          evt.preventDefault();
    +          Filter.assignees.toggle(this.currentData()._id);
    +          Filter.resetExceptions();
    +        },
    +        'click .js-toggle-no-due-date-filter'(evt) {
    +          evt.preventDefault();
    +          Filter.dueAt.noDate();
    +          Filter.resetExceptions();
    +        },
    +        'click .js-toggle-overdue-filter'(evt) {
    +          evt.preventDefault();
    +          Filter.dueAt.past();
    +          Filter.resetExceptions();
    +        },
    +        'click .js-toggle-due-today-filter'(evt) {
    +          evt.preventDefault();
    +          Filter.dueAt.today();
    +          Filter.resetExceptions();
    +        },
    +        'click .js-toggle-due-tomorrow-filter'(evt) {
    +          evt.preventDefault();
    +          Filter.dueAt.tomorrow();
    +          Filter.resetExceptions();
    +        },
    +        'click .js-toggle-due-this-week-filter'(evt) {
    +          evt.preventDefault();
    +          Filter.dueAt.thisWeek();
    +          Filter.resetExceptions();
    +        },
    +        'click .js-toggle-due-next-week-filter'(evt) {
    +          evt.preventDefault();
    +          Filter.dueAt.nextWeek();
    +          Filter.resetExceptions();
    +        },
    +        'click .js-toggle-archive-filter'(evt) {
    +          evt.preventDefault();
    +          Filter.archive.toggle(this.currentData()._id);
    +          Filter.resetExceptions();
    +          const currentBoardId = Session.get('currentBoard');
    +          if (!currentBoardId) return;
    +          subManager.subscribe(
    +            'board',
    +            currentBoardId,
    +            Filter.archive.isSelected(),
    +          );
    +        },
    +        'click .js-toggle-hideEmpty-filter'(evt) {
    +          evt.preventDefault();
    +          Filter.hideEmpty.toggle(this.currentData()._id);
    +          Filter.resetExceptions();
    +        },
    +        'click .js-toggle-custom-fields-filter'(evt) {
    +          evt.preventDefault();
    +          Filter.customFields.toggle(this.currentData()._id);
    +          Filter.resetExceptions();
    +        },
    +        'change .js-field-advanced-filter'(evt) {
    +          evt.preventDefault();
    +          Filter.advanced.set(
    +            this.find('.js-field-advanced-filter').value.trim(),
    +          );
    +          Filter.resetExceptions();
    +        },
    +        'click .js-clear-all'(evt) {
    +          evt.preventDefault();
    +          Filter.reset();
    +        },
    +        'click .js-filter-to-selection'(evt) {
    +          evt.preventDefault();
    +          const selectedCards = ReactiveCache.getCards(Filter.mongoSelector()).map(c => {
    +            return c._id;
    +          });
    +          MultiSelection.add(selectedCards);
    +        },
    +      },
    +    ];
       },
    -  'change .js-field-card-filter'(evt, tpl) {
    -    evt.preventDefault();
    -    Filter.title.set(tpl.find('.js-field-card-filter').value.trim());
    -    Filter.resetExceptions();
    -  },
    -  'click .js-toggle-label-filter'(evt) {
    -    evt.preventDefault();
    -    Filter.labelIds.toggle(Template.currentData()._id);
    -    Filter.resetExceptions();
    -  },
    -  'click .js-toggle-member-filter'(evt) {
    -    evt.preventDefault();
    -    Filter.members.toggle(Template.currentData()._id);
    -    Filter.resetExceptions();
    -  },
    -  'click .js-toggle-assignee-filter'(evt) {
    -    evt.preventDefault();
    -    Filter.assignees.toggle(Template.currentData()._id);
    -    Filter.resetExceptions();
    -  },
    -  'click .js-toggle-no-due-date-filter'(evt) {
    -    evt.preventDefault();
    -    Filter.dueAt.noDate();
    -    Filter.resetExceptions();
    -  },
    -  'click .js-toggle-overdue-filter'(evt) {
    -    evt.preventDefault();
    -    Filter.dueAt.past();
    -    Filter.resetExceptions();
    -  },
    -  'click .js-toggle-due-today-filter'(evt) {
    -    evt.preventDefault();
    -    Filter.dueAt.today();
    -    Filter.resetExceptions();
    -  },
    -  'click .js-toggle-due-tomorrow-filter'(evt) {
    -    evt.preventDefault();
    -    Filter.dueAt.tomorrow();
    -    Filter.resetExceptions();
    -  },
    -  'click .js-toggle-due-this-week-filter'(evt) {
    -    evt.preventDefault();
    -    Filter.dueAt.thisWeek();
    -    Filter.resetExceptions();
    -  },
    -  'click .js-toggle-due-next-week-filter'(evt) {
    -    evt.preventDefault();
    -    Filter.dueAt.nextWeek();
    -    Filter.resetExceptions();
    -  },
    -  'click .js-toggle-archive-filter'(evt) {
    -    evt.preventDefault();
    -    Filter.archive.toggle(Template.currentData()._id);
    -    Filter.resetExceptions();
    -    const currentBoardId = Session.get('currentBoard');
    -    if (!currentBoardId) return;
    -    subManager.subscribe(
    -      'board',
    -      currentBoardId,
    -      Filter.archive.isSelected(),
    -    );
    -  },
    -  'click .js-toggle-hideEmpty-filter'(evt) {
    -    evt.preventDefault();
    -    Filter.hideEmpty.toggle(Template.currentData()._id);
    -    Filter.resetExceptions();
    -  },
    -  'click .js-toggle-custom-fields-filter'(evt) {
    -    evt.preventDefault();
    -    Filter.customFields.toggle(Template.currentData()._id);
    -    Filter.resetExceptions();
    -  },
    -  'change .js-field-advanced-filter'(evt, tpl) {
    -    evt.preventDefault();
    -    Filter.advanced.set(
    -      tpl.find('.js-field-advanced-filter').value.trim(),
    -    );
    -    Filter.resetExceptions();
    -  },
    -  'click .js-clear-all'(evt) {
    -    evt.preventDefault();
    -    Filter.reset();
    -  },
    -  'click .js-filter-to-selection'(evt) {
    -    evt.preventDefault();
    -    const selectedCards = ReactiveCache.getCards(Filter.mongoSelector()).map(c => {
    -      return c._id;
    -    });
    -    MultiSelection.add(selectedCards);
    -  },
    -});
    +}).register('filterSidebar');
     
    -async function mutateSelectedCards(mutationNameOrCallback, ...args) {
    -  const cards = ReactiveCache.getCards(MultiSelection.getMongoSelector(), {sort: ['sort']});
    -  for (const card of cards) {
    -    if (typeof mutationNameOrCallback === 'function') {
    -      await mutationNameOrCallback(card);
    -    } else {
    -      await card[mutationNameOrCallback](...args);
    -    }
    -  }
    -}
    -
    -function getSelectedCardsSorted() {
    -  return ReactiveCache.getCards(MultiSelection.getMongoSelector(), { sort: ['sort'] });
    -}
    -
    -function getListsForBoardSwimlane(boardId, swimlaneId) {
    -  if (!boardId) return [];
    -  const board = ReactiveCache.getBoard(boardId);
    -  if (!board) return [];
    -
    -  const selector = {
    -    boardId,
    -    archived: false,
    -  };
    -
    -  if (swimlaneId) {
    -    const defaultSwimlane = board.getDefaultSwimline && board.getDefaultSwimline();
    -    if (defaultSwimlane && defaultSwimlane._id === swimlaneId) {
    -      selector.swimlaneId = { $in: [swimlaneId, null, ''] };
    -    } else {
    -      selector.swimlaneId = swimlaneId;
    -    }
    -  }
    -
    -  return ReactiveCache.getLists(selector, { sort: { sort: 1 } });
    -}
    -
    -function getMaxSortForList(listId, swimlaneId) {
    -  if (!listId || !swimlaneId) return null;
    -  const card = ReactiveCache.getCard(
    -    { listId, swimlaneId, archived: false },
    -    { sort: { sort: -1 } },
    -    true,
    -  );
    -  return card ? card.sort : null;
    -}
    -
    -function buildInsertionSortIndexes(cardsCount, targetCard, position, listId, swimlaneId) {
    -  const indexes = [];
    -  if (cardsCount <= 0) return indexes;
    -
    -  if (targetCard) {
    -    const step = 0.5;
    -    if (position === 'above') {
    -      const start = targetCard.sort - step * cardsCount;
    -      for (let i = 0; i < cardsCount; i += 1) {
    -        indexes.push(start + step * i);
    -      }
    -    } else {
    -      const start = targetCard.sort + step;
    -      for (let i = 0; i < cardsCount; i += 1) {
    -        indexes.push(start + step * i);
    -      }
    -    }
    -    return indexes;
    -  }
    -
    -  const maxSort = getMaxSortForList(listId, swimlaneId);
    -  const start = maxSort === null ? 0 : maxSort + 1;
    -  for (let i = 0; i < cardsCount; i += 1) {
    -    indexes.push(start + i);
    -  }
    -  return indexes;
    -}
    -
    -function mapSelection(kind, _id) {
    -  return ReactiveCache.getCards(MultiSelection.getMongoSelector(), {sort: ['sort']}).map(card => {
    -    const methodName = kind === 'label' ? 'hasLabel' : 'isAssigned';
    -    return card[methodName](_id);
    +function mutateSelectedCards(mutationName, ...args) {
    +  ReactiveCache.getCards(MultiSelection.getMongoSelector(), {sort: ['sort']}).forEach(card => {
    +    card[mutationName](...args);
       });
     }
     
    +BlazeComponent.extendComponent({
    +  mapSelection(kind, _id) {
    +    return ReactiveCache.getCards(MultiSelection.getMongoSelector(), {sort: ['sort']}).map(card => {
    +      const methodName = kind === 'label' ? 'hasLabel' : 'isAssigned';
    +      return card[methodName](_id);
    +    });
    +  },
    +
    +  allSelectedElementHave(kind, _id) {
    +    if (MultiSelection.isEmpty()) return false;
    +    else return _.every(this.mapSelection(kind, _id));
    +  },
    +
    +  someSelectedElementHave(kind, _id) {
    +    if (MultiSelection.isEmpty()) return false;
    +    else return _.some(this.mapSelection(kind, _id));
    +  },
    +
    +  events() {
    +    return [
    +      {
    +        'click .js-toggle-label-multiselection'(evt) {
    +          const labelId = this.currentData()._id;
    +          const mappedSelection = this.mapSelection('label', labelId);
    +
    +          if (_.every(mappedSelection)) {
    +            mutateSelectedCards('removeLabel', labelId);
    +          } else if (_.every(mappedSelection, bool => !bool)) {
    +            mutateSelectedCards('addLabel', labelId);
    +          } else {
    +            const popup = Popup.open('disambiguateMultiLabel');
    +            // XXX We need to have a better integration between the popup and the
    +            // UI components systems.
    +            popup.call(this.currentData(), evt);
    +          }
    +        },
    +        'click .js-toggle-member-multiselection'(evt) {
    +          const memberId = this.currentData()._id;
    +          const mappedSelection = this.mapSelection('member', memberId);
    +          if (_.every(mappedSelection)) {
    +            mutateSelectedCards('unassignMember', memberId);
    +          } else if (_.every(mappedSelection, bool => !bool)) {
    +            mutateSelectedCards('assignMember', memberId);
    +          } else {
    +            const popup = Popup.open('disambiguateMultiMember');
    +            // XXX We need to have a better integration between the popup and the
    +            // UI components systems.
    +            popup.call(this.currentData(), evt);
    +          }
    +        },
    +        'click .js-move-selection': Popup.open('moveSelection'),
    +        'click .js-archive-selection'() {
    +          mutateSelectedCards('archive');
    +          EscapeActions.executeUpTo('multiselection');
    +        },
    +      },
    +    ];
    +  },
    +}).register('multiselectionSidebar');
    +
     Template.multiselectionSidebar.helpers({
       isBoardAdmin() {
         return ReactiveCache.getCurrentUser().isBoardAdmin();
    @@ -189,53 +178,6 @@ Template.multiselectionSidebar.helpers({
       isCommentOnly() {
         return ReactiveCache.getCurrentUser().isCommentOnly();
       },
    -  allSelectedElementHave(kind, _id) {
    -    if (MultiSelection.isEmpty()) return false;
    -    else return _.every(mapSelection(kind, _id));
    -  },
    -  someSelectedElementHave(kind, _id) {
    -    if (MultiSelection.isEmpty()) return false;
    -    else return _.some(mapSelection(kind, _id));
    -  },
    -});
    -
    -Template.multiselectionSidebar.events({
    -  'click .js-toggle-label-multiselection'(evt) {
    -    const labelId = Template.currentData()._id;
    -    const mappedSelection = mapSelection('label', labelId);
    -
    -    if (_.every(mappedSelection)) {
    -      mutateSelectedCards('removeLabel', labelId);
    -    } else if (_.every(mappedSelection, bool => !bool)) {
    -      mutateSelectedCards('addLabel', labelId);
    -    } else {
    -      const popup = Popup.open('disambiguateMultiLabel');
    -      // XXX We need to have a better integration between the popup and the
    -      // UI components systems.
    -      popup.call(Template.currentData(), evt);
    -    }
    -  },
    -  'click .js-toggle-member-multiselection'(evt) {
    -    const memberId = Template.currentData()._id;
    -    const mappedSelection = mapSelection('member', memberId);
    -    if (_.every(mappedSelection)) {
    -      mutateSelectedCards('unassignMember', memberId);
    -    } else if (_.every(mappedSelection, bool => !bool)) {
    -      mutateSelectedCards('assignMember', memberId);
    -    } else {
    -      const popup = Popup.open('disambiguateMultiMember');
    -      // XXX We need to have a better integration between the popup and the
    -      // UI components systems.
    -      popup.call(Template.currentData(), evt);
    -    }
    -  },
    -  'click .js-move-selection': Popup.open('moveSelection'),
    -  'click .js-copy-selection': Popup.open('copySelection'),
    -  'click .js-selection-color': Popup.open('setSelectionColor'),
    -  'click .js-archive-selection'() {
    -    mutateSelectedCards('archive');
    -    EscapeActions.executeUpTo('multiselection');
    -  },
     });
     
     Template.disambiguateMultiLabelPopup.events({
    @@ -260,321 +202,10 @@ Template.disambiguateMultiMemberPopup.events({
       },
     });
     
    -Template.moveSelectionPopup.onCreated(function() {
    -  this.selectedBoardId = new ReactiveVar(Session.get('currentBoard'));
    -  this.selectedSwimlaneId = new ReactiveVar('');
    -  this.selectedListId = new ReactiveVar('');
    -  this.selectedCardId = new ReactiveVar('');
    -  this.position = new ReactiveVar('above');
    -
    -  this.getBoardData = function(boardId) {
    -    const self = this;
    -    Meteor.subscribe('board', boardId, false, {
    -      onReady() {
    -        const sameBoardId = self.selectedBoardId.get() === boardId;
    -        self.selectedBoardId.set(boardId);
    -
    -        if (!sameBoardId) {
    -          self.setFirstSwimlaneId();
    -          self.setFirstListId();
    -        }
    -      },
    -    });
    -  };
    -
    -  this.setFirstSwimlaneId = function() {
    -    try {
    -      const board = ReactiveCache.getBoard(this.selectedBoardId.get());
    -      const swimlaneId = board.swimlanes()[0]._id;
    -      this.selectedSwimlaneId.set(swimlaneId);
    -    } catch (e) {}
    -  };
    -
    -  this.setFirstListId = function() {
    -    try {
    -      const boardId = this.selectedBoardId.get();
    -      const swimlaneId = this.selectedSwimlaneId.get();
    -      const lists = getListsForBoardSwimlane(boardId, swimlaneId);
    -      const listId = lists[0] ? lists[0]._id : '';
    -      this.selectedListId.set(listId);
    -      this.selectedCardId.set('');
    -    } catch (e) {}
    -  };
    -
    -  this.getBoardData(Session.get('currentBoard'));
    -  this.setFirstSwimlaneId();
    -  this.setFirstListId();
    -});
    -
    -Template.moveSelectionPopup.helpers({
    -  boards() {
    -    return ReactiveCache.getBoards(
    -      {
    -        archived: false,
    -        'members.userId': Meteor.userId(),
    -        _id: { $ne: ReactiveCache.getCurrentUser().getTemplatesBoardId() },
    -      },
    -      {
    -        sort: { sort: 1 },
    -      },
    -    );
    -  },
    -  swimlanes() {
    -    const board = ReactiveCache.getBoard(Template.instance().selectedBoardId.get());
    -    return board ? board.swimlanes() : [];
    -  },
    -  lists() {
    -    const instance = Template.instance();
    -    return getListsForBoardSwimlane(
    -      instance.selectedBoardId.get(),
    -      instance.selectedSwimlaneId.get(),
    -    );
    -  },
    -  cards() {
    -    const instance = Template.instance();
    -    const list = ReactiveCache.getList(instance.selectedListId.get());
    -    if (!list) return [];
    -    return list.cards(instance.selectedSwimlaneId.get()).sort((a, b) => a.sort - b.sort);
    -  },
    -  isDialogOptionBoardId(boardId) {
    -    return Template.instance().selectedBoardId.get() === boardId;
    -  },
    -  isDialogOptionSwimlaneId(swimlaneId) {
    -    return Template.instance().selectedSwimlaneId.get() === swimlaneId;
    -  },
    -  isDialogOptionListId(listId) {
    -    return Template.instance().selectedListId.get() === listId;
    -  },
    -  isTitleDefault(title) {
    -    if (
    -      title.startsWith("key 'default") &&
    -      title.endsWith('returned an object instead of string.')
    -    ) {
    -      const translated = `${TAPi18n.__('defaultdefault')}`;
    -      if (
    -        translated.startsWith("key 'default") &&
    -        translated.endsWith('returned an object instead of string.')
    -      ) {
    -        return 'Default';
    -      }
    -      return translated;
    -    }
    -    if (title === 'Default') {
    -      return `${TAPi18n.__('defaultdefault')}`;
    -    }
    -    return title;
    -  },
    -});
    -
     Template.moveSelectionPopup.events({
    -  'change .js-select-boards'(event) {
    -    const boardId = $(event.currentTarget).val();
    -    Template.instance().getBoardData(boardId);
    -  },
    -  'change .js-select-swimlanes'(event) {
    -    const instance = Template.instance();
    -    instance.selectedSwimlaneId.set($(event.currentTarget).val());
    -    instance.setFirstListId();
    -  },
    -  'change .js-select-lists'(event) {
    -    const instance = Template.instance();
    -    instance.selectedListId.set($(event.currentTarget).val());
    -    instance.selectedCardId.set('');
    -  },
    -  'change .js-select-cards'(event) {
    -    Template.instance().selectedCardId.set($(event.currentTarget).val());
    -  },
    -  'change input[name="position"]'(event) {
    -    Template.instance().position.set($(event.currentTarget).val());
    -  },
    -  async 'click .js-done'() {
    -    const instance = Template.instance();
    -    const boardId = instance.selectedBoardId.get();
    -    const swimlaneId = instance.selectedSwimlaneId.get();
    -    const listId = instance.selectedListId.get();
    -    const cardId = instance.selectedCardId.get();
    -    const position = instance.position.get();
    -
    -    const selectedCards = getSelectedCardsSorted();
    -    const targetCard = cardId ? ReactiveCache.getCard(cardId) : null;
    -    const sortIndexes = buildInsertionSortIndexes(
    -      selectedCards.length,
    -      targetCard,
    -      position,
    -      listId,
    -      swimlaneId,
    -    );
    -
    -    for (let i = 0; i < selectedCards.length; i += 1) {
    -      await selectedCards[i].move(boardId, swimlaneId, listId, sortIndexes[i]);
    -    }
    -    EscapeActions.executeUpTo('multiselection');
    -  },
    -});
    -
    -Template.copySelectionPopup.onCreated(function() {
    -  this.selectedBoardId = new ReactiveVar(Session.get('currentBoard'));
    -  this.selectedSwimlaneId = new ReactiveVar('');
    -  this.selectedListId = new ReactiveVar('');
    -  this.selectedCardId = new ReactiveVar('');
    -  this.position = new ReactiveVar('above');
    -
    -  this.getBoardData = function(boardId) {
    -    const self = this;
    -    Meteor.subscribe('board', boardId, false, {
    -      onReady() {
    -        const sameBoardId = self.selectedBoardId.get() === boardId;
    -        self.selectedBoardId.set(boardId);
    -
    -        if (!sameBoardId) {
    -          self.setFirstSwimlaneId();
    -          self.setFirstListId();
    -        }
    -      },
    -    });
    -  };
    -
    -  this.setFirstSwimlaneId = function() {
    -    try {
    -      const board = ReactiveCache.getBoard(this.selectedBoardId.get());
    -      const swimlaneId = board.swimlanes()[0]._id;
    -      this.selectedSwimlaneId.set(swimlaneId);
    -    } catch (e) {}
    -  };
    -
    -  this.setFirstListId = function() {
    -    try {
    -      const boardId = this.selectedBoardId.get();
    -      const swimlaneId = this.selectedSwimlaneId.get();
    -      const lists = getListsForBoardSwimlane(boardId, swimlaneId);
    -      const listId = lists[0] ? lists[0]._id : '';
    -      this.selectedListId.set(listId);
    -      this.selectedCardId.set('');
    -    } catch (e) {}
    -  };
    -
    -  this.getBoardData(Session.get('currentBoard'));
    -  this.setFirstSwimlaneId();
    -  this.setFirstListId();
    -});
    -
    -Template.copySelectionPopup.helpers({
    -  boards() {
    -    return ReactiveCache.getBoards(
    -      {
    -        archived: false,
    -        'members.userId': Meteor.userId(),
    -        _id: { $ne: ReactiveCache.getCurrentUser().getTemplatesBoardId() },
    -      },
    -      {
    -        sort: { sort: 1 },
    -      },
    -    );
    -  },
    -  swimlanes() {
    -    const board = ReactiveCache.getBoard(Template.instance().selectedBoardId.get());
    -    return board ? board.swimlanes() : [];
    -  },
    -  lists() {
    -    const instance = Template.instance();
    -    return getListsForBoardSwimlane(
    -      instance.selectedBoardId.get(),
    -      instance.selectedSwimlaneId.get(),
    -    );
    -  },
    -  cards() {
    -    const instance = Template.instance();
    -    const list = ReactiveCache.getList(instance.selectedListId.get());
    -    if (!list) return [];
    -    return list.cards(instance.selectedSwimlaneId.get()).sort((a, b) => a.sort - b.sort);
    -  },
    -  isDialogOptionBoardId(boardId) {
    -    return Template.instance().selectedBoardId.get() === boardId;
    -  },
    -  isDialogOptionSwimlaneId(swimlaneId) {
    -    return Template.instance().selectedSwimlaneId.get() === swimlaneId;
    -  },
    -  isDialogOptionListId(listId) {
    -    return Template.instance().selectedListId.get() === listId;
    -  },
    -  isTitleDefault(title) {
    -    if (
    -      title.startsWith("key 'default") &&
    -      title.endsWith('returned an object instead of string.')
    -    ) {
    -      const translated = `${TAPi18n.__('defaultdefault')}`;
    -      if (
    -        translated.startsWith("key 'default") &&
    -        translated.endsWith('returned an object instead of string.')
    -      ) {
    -        return 'Default';
    -      }
    -      return translated;
    -    }
    -    if (title === 'Default') {
    -      return `${TAPi18n.__('defaultdefault')}`;
    -    }
    -    return title;
    -  },
    -});
    -
    -Template.copySelectionPopup.events({
    -  'change .js-select-boards'(event) {
    -    const boardId = $(event.currentTarget).val();
    -    Template.instance().getBoardData(boardId);
    -  },
    -  'change .js-select-swimlanes'(event) {
    -    const instance = Template.instance();
    -    instance.selectedSwimlaneId.set($(event.currentTarget).val());
    -    instance.setFirstListId();
    -  },
    -  'change .js-select-lists'(event) {
    -    const instance = Template.instance();
    -    instance.selectedListId.set($(event.currentTarget).val());
    -    instance.selectedCardId.set('');
    -  },
    -  'change .js-select-cards'(event) {
    -    Template.instance().selectedCardId.set($(event.currentTarget).val());
    -  },
    -  'change input[name="position"]'(event) {
    -    Template.instance().position.set($(event.currentTarget).val());
    -  },
    -  async 'click .js-done'() {
    -    const instance = Template.instance();
    -    const boardId = instance.selectedBoardId.get();
    -    const swimlaneId = instance.selectedSwimlaneId.get();
    -    const listId = instance.selectedListId.get();
    -    const cardId = instance.selectedCardId.get();
    -    const position = instance.position.get();
    -
    -    const selectedCards = getSelectedCardsSorted();
    -    const targetCard = cardId ? ReactiveCache.getCard(cardId) : null;
    -    const sortIndexes = buildInsertionSortIndexes(
    -      selectedCards.length,
    -      targetCard,
    -      position,
    -      listId,
    -      swimlaneId,
    -    );
    -
    -    for (let i = 0; i < selectedCards.length; i += 1) {
    -      const card = selectedCards[i];
    -      const newCardId = await Meteor.callAsync(
    -        'copyCard',
    -        card._id,
    -        boardId,
    -        swimlaneId,
    -        listId,
    -        true,
    -        { title: card.title },
    -      );
    -      if (!newCardId) continue;
    -
    -      const newCard = ReactiveCache.getCard(newCardId);
    -      if (!newCard) continue;
    -
    -      await newCard.move(boardId, swimlaneId, listId, sortIndexes[i]);
    -    }
    +  'click .js-select-list'() {
    +    // Move the minicard to the end of the target list
    +    mutateSelectedCards('moveToEndOfList', { listId: this._id });
         EscapeActions.executeUpTo('multiselection');
       },
     });
    diff --git a/client/components/sidebar/sidebarSearches.js b/client/components/sidebar/sidebarSearches.js
    index 53b954626..a6e649ffb 100644
    --- a/client/components/sidebar/sidebarSearches.js
    +++ b/client/components/sidebar/sidebarSearches.js
    @@ -1,31 +1,41 @@
    -Template.searchSidebar.onCreated(function () {
    -  this.term = new ReactiveVar('');
    -});
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.term = new ReactiveVar('');
    +  },
     
    -Template.searchSidebar.helpers({
       cards() {
         const currentBoard = Utils.getCurrentBoard();
    -    return currentBoard.searchCards(Template.instance().term.get());
    +    return currentBoard.searchCards(this.term.get());
       },
     
       lists() {
         const currentBoard = Utils.getCurrentBoard();
    -    return currentBoard.searchLists(Template.instance().term.get());
    +    return currentBoard.searchLists(this.term.get());
       },
    -});
     
    -Template.searchSidebar.events({
    -  'click .js-minicard'(evt) {
    +  clickOnMiniCard(evt) {
         if (Utils.isMiniScreen()) {
           evt.preventDefault();
    -      Session.set('popupCardId', Template.currentData()._id);
    -      if (!Popup.isOpen()) {
    -        Popup.open("cardDetails")(evt);
    -      }
    +      Session.set('popupCardId', this.currentData()._id);
    +      this.cardDetailsPopup(evt);
         }
       },
    -  'submit .js-search-term-form'(evt, tpl) {
    -    evt.preventDefault();
    -    tpl.term.set(evt.target.searchTerm.value);
    +
    +  cardDetailsPopup(event) {
    +    if (!Popup.isOpen()) {
    +      Popup.open("cardDetails")(event);
    +    }
       },
    -});
    +
    +  events() {
    +    return [
    +      {
    +        'click .js-minicard': this.clickOnMiniCard,
    +        'submit .js-search-term-form'(evt) {
    +          evt.preventDefault();
    +          this.term.set(evt.target.searchTerm.value);
    +        },
    +      },
    +    ];
    +  },
    +}).register('searchSidebar');
    diff --git a/client/components/swimlanes/miniswimlane.jade b/client/components/swimlanes/miniswimlane.jade
    index fda17c8ca..890187795 100644
    --- a/client/components/swimlanes/miniswimlane.jade
    +++ b/client/components/swimlanes/miniswimlane.jade
    @@ -3,6 +3,6 @@ template(name="miniswimlane")
         class="minicard-{{colorClass}}")
         .minicard-title
           .handle
    -        i.fa.fa-arrows
    +        | ↕️
           +viewer
             = title
    diff --git a/client/components/swimlanes/swimlaneHeader.jade b/client/components/swimlanes/swimlaneHeader.jade
    index 5a06dc158..04548ffa6 100644
    --- a/client/components/swimlanes/swimlaneHeader.jade
    +++ b/client/components/swimlanes/swimlaneHeader.jade
    @@ -25,25 +25,25 @@ template(name="swimlaneFixedHeader")
       .swimlane-header-menu
         if currentUser
           unless currentUser.isCommentOnly
    -        unless currentUser.isReadOnly
    -          unless currentUser.isReadAssignedOnly
    -            unless currentUser.isWorker
    -              a.swimlane-collapse-indicator.js-collapse-swimlane.swimlane-header-collapse(title="{{_ 'collapse'}}")
    -                if collapseSwimlane
    -                  i.fa.fa-caret-right
    -                else
    -                  i.fa.fa-caret-down
    -              a.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}")
    -                i.fa.fa-bars
    -              a.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}")
    -                i.fa.fa-plus
    -              if isTouchScreenOrShowDesktopDragHandles
    -                unless isTouchScreen
    -                  a.swimlane-header-handle.handle.js-swimlane-header-handle
    -                    i.fa.fa-arrows
    -                if isTouchScreen
    -                  a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle
    -                    i.fa.fa-arrows
    +        a.js-open-add-swimlane-menu.swimlane-header-plus-icon(title="{{_ 'add-swimlane'}}")
    +          | ➕
    +        a.js-open-swimlane-menu(title="{{_ 'swimlaneActionPopup-title'}}")
    +          | ☰
    +        //// TODO: Collapse Swimlane: make button working, etc.
    +        //unless collapsed
    +        //  a.js-collapse-swimlane(title="{{_ 'collapse'}}")
    +        //    i.fa.fa-arrow-down.swimlane-header-collapse-down
    +        //    ⬆️.swimlane-header-collapse-up
    +        //if collapsed
    +        //  a.js-collapse-swimlane(title="{{_ 'uncollapse'}}")
    +        //    ⬆️.swimlane-header-collapse-up
    +        //    i.fa.fa-arrow-down.swimlane-header-collapse-down
    +        unless isTouchScreen
    +          a.swimlane-header-handle.handle.js-swimlane-header-handle
    +            | ↕️
    +        if isTouchScreen
    +          a.swimlane-header-miniscreen-handle.handle.js-swimlane-header-handle
    +            | ↕️
     
     template(name="editSwimlaneTitleForm")
       .list-composer
    @@ -51,64 +51,49 @@ template(name="editSwimlaneTitleForm")
         .edit-controls.clearfix
           button.primary.confirm(type="submit") {{_ 'save'}}
           a.js-close-inlined-form
    -        i.fa.fa-times-thin
    +        | ❌
     
     template(name="swimlaneActionPopup")
       if currentUser
         unless currentUser.isCommentOnly
    -      unless currentUser.isReadOnly
    -        unless currentUser.isReadAssignedOnly
    -          ul.pop-over-list
    -             li: a.js-add-swimlane
    -               i.fa.fa-plus
    -               span
    -                 | {{_ 'add-swimlane'}}
    +      ul.pop-over-list
    +         if currentUser.isBoardAdmin
    +           li: a.js-set-swimlane-color
    +             | 🎨
    +             | {{_ 'select-color'}}
    +         li: a.js-set-swimlane-height
    +           | ↕️
    +           |  {{_ 'set-swimlane-height'}}
    +      if currentUser.isBoardAdmin
    +        unless this.isTemplateContainer
               hr
               ul.pop-over-list
    -             li: a.js-add-list-from-swimlane
    -               i.fa.fa-plus
    -               span
    -                 | {{_ 'add-list'}}
    -          hr
    +            li: a.js-close-swimlane
    +              | ▶️
    +              | 📦
    +              | {{_ 'archive-swimlane'}}
               ul.pop-over-list
    -             if currentUser.isBoardAdmin
    -               li: a.js-set-swimlane-color
    -                 i.fa.fa-paint-brush
    -                 | {{_ 'select-color'}}
    -             li: a.js-set-swimlane-height
    -               i.fa.fa-arrows
    -               |  {{_ 'set-swimlane-height'}}
    -          if currentUser.isBoardAdmin
    -            unless this.isTemplateContainer
    -              hr
    -              ul.pop-over-list
    -                li: a.js-close-swimlane
    -                  i.fa.fa-archive
    -                  | {{_ 'archive-swimlane'}}
    -              ul.pop-over-list
    -                li: a.js-copy-swimlane
    -                  i.fa.fa-clipboard
    -                  | {{_ 'copy-swimlane'}}
    -              ul.pop-over-list
    -                li: a.js-move-swimlane
    -                  i.fa.fa-arrow-up
    -                  | {{_ 'move-swimlane'}}
    +            li: a.js-copy-swimlane
    +              | 📋
    +              | {{_ 'copy-swimlane'}}
    +          ul.pop-over-list
    +            li: a.js-move-swimlane
    +              | ⬆️
    +              | {{_ 'move-swimlane'}}
     
     template(name="swimlaneAddPopup")
       if currentUser
         unless currentUser.isCommentOnly
    -      unless currentUser.isReadOnly
    -        unless currentUser.isReadAssignedOnly
    -          form
    -            input.swimlane-name-input.full-line(type="text" placeholder="{{_ 'add-swimlane'}}"
    -              autocomplete="off" autofocus)
    -            .edit-controls.clearfix
    -              button.primary.confirm(type="submit") {{_ 'add'}}
    -              unless currentBoard.isTemplatesBoard
    -                unless currentBoard.isTemplateBoard
    -                  span.quiet
    -                    | {{_ 'or'}}
    -                    a.js-swimlane-template {{_ 'template'}}
    +      form
    +        input.swimlane-name-input.full-line(type="text" placeholder="{{_ 'add-swimlane'}}"
    +          autocomplete="off" autofocus)
    +        .edit-controls.clearfix
    +          button.primary.confirm(type="submit") {{_ 'add'}}
    +          unless currentBoard.isTemplatesBoard
    +            unless currentBoard.isTemplateBoard
    +              span.quiet
    +                | {{_ 'or'}}
    +                a.js-swimlane-template {{_ 'template'}}
     
     template(name="setSwimlaneColorPopup")
       form.edit-label.swimlane-color-popup
    @@ -118,7 +103,7 @@ template(name="setSwimlaneColorPopup")
             each colors
               span.card-label.palette-color.js-palette-color(class="card-details-{{color}}")
                 if(isSelected color)
    -              i.fa.fa-check
    +              | ✅
         // Buttons aligned left too
         .flush-left
           button.primary.confirm.js-submit(style="margin-left:0") {{_ 'save'}}
    diff --git a/client/components/swimlanes/swimlaneHeader.js b/client/components/swimlanes/swimlaneHeader.js
    index abf061ae6..c0ef35453 100644
    --- a/client/components/swimlanes/swimlaneHeader.js
    +++ b/client/components/swimlanes/swimlaneHeader.js
    @@ -7,43 +7,48 @@ Meteor.startup(() => {
       swimlaneColors = Swimlanes.simpleSchema()._schema.color.allowedValues;
     });
     
    -function swimlaneHeaderCollapsed(check = undefined) {
    -  const swimlane = Template.currentData();
    -  const status = Utils.getSwimlaneCollapseState(swimlane);
    -  if (check === undefined) {
    -    return status;
    -  } else {
    -    const next = typeof check === 'boolean' ? check : !status;
    -    Utils.setSwimlaneCollapseState(swimlane, next);
    -    return next;
    -  }
    -}
    -
    -Template.swimlaneHeader.events({
    -  'click .js-collapse-swimlane'(event) {
    +BlazeComponent.extendComponent({
    +  editTitle(event) {
         event.preventDefault();
    -    swimlaneHeaderCollapsed(!swimlaneHeaderCollapsed());
    -  },
    -  'click .js-open-swimlane-menu': Popup.open('swimlaneAction'),
    -  'click .js-open-add-swimlane-menu': Popup.open('swimlaneAdd'),
    -  async submit(event, tpl) {
    -    event.preventDefault();
    -    const newTitle = tpl.$('.list-name-input').val().trim();
    -    const swimlane = Template.currentData();
    +    const newTitle = this.childComponents('inlinedForm')[0]
    +      .getValue()
    +      .trim();
    +    const swimlane = this.currentData();
         if (newTitle) {
    -      await swimlane.rename(newTitle.trim());
    +      swimlane.rename(newTitle.trim());
         }
       },
    -});
    +  collapsed(check = undefined) {
    +    const swimlane = Template.currentData();
    +    const status = swimlane.isCollapsed();
    +    if (check === undefined) {
    +      // just check
    +      return status;
    +    } else {
    +      swimlane.collapse(!status);
    +      return !status;
    +    }
    +  },
    +
    +  events() {
    +    return [
    +      {
    +        'click .js-collapse-swimlane'(event) {
    +          event.preventDefault();
    +          this.collapsed(!this.collapsed());
    +        },
    +        'click .js-open-swimlane-menu': Popup.open('swimlaneAction'),
    +        'click .js-open-add-swimlane-menu': Popup.open('swimlaneAdd'),
    +        submit: this.editTitle,
    +      },
    +    ];
    +  },
    +}).register('swimlaneHeader');
     
     Template.swimlaneFixedHeader.helpers({
       isBoardAdmin() {
         return ReactiveCache.getCurrentUser().isBoardAdmin();
       },
    -  collapseSwimlane() {
    -    const swimlane = Template.currentData();
    -    return Utils.getSwimlaneCollapseState(swimlane);
    -  },
       isTitleDefault(title) {
         // https://github.com/wekan/wekan/issues/4763
         // https://github.com/wekan/wekan/issues/4742
    @@ -76,7 +81,7 @@ Template.editSwimlaneTitleForm.helpers({
         // When that happens, try use translation "defaultdefault" that has same content of default, or return text "Default".
         // This can happen, if swimlane does not have name.
         // Yes, this is fixing the symptom (Swimlane title does not have title)
    -    // instead of fixing the problem (Add Swimlane title when creating swimlane)
    +    // instead of fixing the problem (Add Swimlane title when creating swimlane) 
         // because there could be thousands of swimlanes, adding name Default to all of them
         // would be very slow.
         if (title.startsWith("key 'default") && title.endsWith('returned an object instead of string.')) {
    @@ -94,13 +99,11 @@ Template.editSwimlaneTitleForm.helpers({
     });
     
     Template.swimlaneActionPopup.events({
    -  'click .js-add-swimlane': Popup.open('swimlaneAdd'),
    -  'click .js-add-list-from-swimlane': Popup.open('addList'),
       'click .js-set-swimlane-color': Popup.open('setSwimlaneColor'),
       'click .js-set-swimlane-height': Popup.open('setSwimlaneHeight'),
    -  async 'click .js-close-swimlane'(event) {
    +  'click .js-close-swimlane'(event) {
         event.preventDefault();
    -    await this.archive();
    +    this.archive();
         Popup.back();
       },
       'click .js-move-swimlane': Popup.open('moveSwimlane'),
    @@ -113,105 +116,128 @@ Template.swimlaneActionPopup.events({
       },
     });
     
    -Template.swimlaneAddPopup.onCreated(function () {
    -  this.currentSwimlane = Template.currentData();
    -});
    -
    -Template.swimlaneAddPopup.events({
    -  submit(event, tpl) {
    -    event.preventDefault();
    -    const currentBoard = Utils.getCurrentBoard();
    -    const nextSwimlane = currentBoard.nextSwimlane(tpl.currentSwimlane);
    -    const titleInput = tpl.find('.swimlane-name-input');
    -    const title = titleInput.value.trim();
    -    const sortValue = calculateIndexData(
    -      tpl.currentSwimlane,
    -      nextSwimlane,
    -      1,
    -    );
    -    const swimlaneType = currentBoard.isTemplatesBoard()
    -      ? 'template-swimlane'
    -      : 'swimlane';
    -
    -    if (title) {
    -      Swimlanes.insert({
    -        title,
    -        boardId: Session.get('currentBoard'),
    -        sort: sortValue.base || 0,
    -        type: swimlaneType,
    -      });
    -
    -      titleInput.value = '';
    -      titleInput.focus();
    -    }
    -    // XXX ideally, we should move the popup to the newly
    -    // created swimlane so a user can add more than one swimlane
    -    // with a minimum of interactions
    -    Popup.back();
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.currentSwimlane = this.currentData();
       },
    -  'click .js-swimlane-template': Popup.open('searchElement'),
    -});
     
    -Template.setSwimlaneColorPopup.onCreated(function () {
    -  this.currentSwimlane = Template.currentData();
    -  this.currentColor = new ReactiveVar(this.currentSwimlane.color);
    -});
    +  events() {
    +    return [
    +      {
    +        submit(event) {
    +          event.preventDefault();
    +          const currentBoard = Utils.getCurrentBoard();
    +          const nextSwimlane = currentBoard.nextSwimlane(this.currentSwimlane);
    +          const titleInput = this.find('.swimlane-name-input');
    +          const title = titleInput.value.trim();
    +          const sortValue = calculateIndexData(
    +            this.currentSwimlane,
    +            nextSwimlane,
    +            1,
    +          );
    +          const swimlaneType = currentBoard.isTemplatesBoard()
    +            ? 'template-swimlane'
    +            : 'swimlane';
    +
    +          if (title) {
    +            Swimlanes.insert({
    +              title,
    +              boardId: Session.get('currentBoard'),
    +              sort: sortValue.base || 0,
    +              type: swimlaneType,
    +            });
    +
    +            titleInput.value = '';
    +            titleInput.focus();
    +          }
    +          // XXX ideally, we should move the popup to the newly
    +          // created swimlane so a user can add more than one swimlane
    +          // with a minimum of interactions
    +          Popup.back();
    +        },
    +        'click .js-swimlane-template': Popup.open('searchElement'),
    +      },
    +    ];
    +  },
    +}).register('swimlaneAddPopup');
    +
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.currentSwimlane = this.currentData();
    +    this.currentColor = new ReactiveVar(this.currentSwimlane.color);
    +  },
     
    -Template.setSwimlaneColorPopup.helpers({
       colors() {
         return swimlaneColors.map(color => ({ color, name: '' }));
       },
    +
       isSelected(color) {
    -    return Template.instance().currentColor.get() === color;
    +    return this.currentColor.get() === color;
       },
    -});
     
    -Template.setSwimlaneColorPopup.events({
    -  async 'submit form'(event, tpl) {
    -    event.preventDefault();
    -    await tpl.currentSwimlane.setColor(tpl.currentColor.get());
    -    Popup.back();
    +  events() {
    +    return [
    +      {
    +        'submit form'(event) {
    +          event.preventDefault();
    +          this.currentSwimlane.setColor(this.currentColor.get());
    +          Popup.back();
    +        },
    +        'click .js-palette-color'() {
    +          this.currentColor.set(this.currentData().color);
    +        },
    +        'click .js-submit'() {
    +          this.currentSwimlane.setColor(this.currentColor.get());
    +          Popup.back();
    +        },
    +        'click .js-remove-color'() {
    +          this.currentSwimlane.setColor(null);
    +          Popup.back();
    +        },
    +      },
    +    ];
       },
    -  'click .js-palette-color'(event, tpl) {
    -    tpl.currentColor.set(Template.currentData().color);
    -  },
    -  async 'click .js-submit'(event, tpl) {
    -    await tpl.currentSwimlane.setColor(tpl.currentColor.get());
    -    Popup.back();
    -  },
    -  async 'click .js-remove-color'(event, tpl) {
    -    await tpl.currentSwimlane.setColor(null);
    -    Popup.back();
    -  },
    -});
    +}).register('setSwimlaneColorPopup');
     
    -Template.setSwimlaneHeightPopup.onCreated(function () {
    -  this.currentSwimlane = Template.currentData();
    -});
    -
    -Template.setSwimlaneHeightPopup.helpers({
    -  swimlaneHeightValue() {
    -    const swimlane = Template.currentData();
    -    const board = swimlane.boardId;
    -    return ReactiveCache.getCurrentUser().getSwimlaneHeight(board, swimlane._id);
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.currentSwimlane = this.currentData();
       },
    -});
     
    -Template.setSwimlaneHeightPopup.events({
    -  'click .swimlane-height-apply'(event, tpl) {
    -    const swimlane = Template.currentData();
    +  applySwimlaneHeight() {
    +    const swimlane = this.currentData();
         const board = swimlane.boardId;
         const height = parseInt(
    -      tpl.$('.swimlane-height-value').val(),
    +      Template.instance()
    +        .$('.swimlane-height-value')
    +        .val(),
           10,
         );
     
    +    // FIXME(mark-i-m): where do we put constants?
    +    //                  also in imports/i18n/data/en.i18n.json
         if (height != -1 && (height < 100 || !height)) {
    -      tpl.$('.swimlane-height-error').click();
    +      Template.instance()
    +        .$('.swimlane-height-error')
    +        .click();
         } else {
           Meteor.call('applySwimlaneHeight', board, swimlane._id, height);
           Popup.back();
         }
       },
    -  'click .swimlane-height-error': Popup.open('swimlaneHeightError'),
    -});
    +
    +  swimlaneHeightValue() {
    +    const swimlane = this.currentData();
    +    const board = swimlane.boardId;
    +    return ReactiveCache.getCurrentUser().getSwimlaneHeight(board, swimlane._id);
    +  },
    +
    +  events() {
    +    return [
    +      {
    +        'click .swimlane-height-apply': this.applySwimlaneHeight,
    +        'click .swimlane-height-error': Popup.open('swimlaneHeightError'),
    +      },
    +    ];
    +  },
    +}).register('setSwimlaneHeightPopup');
    diff --git a/client/components/swimlanes/swimlanes.css b/client/components/swimlanes/swimlanes.css
    index 4c35b3580..83540549f 100644
    --- a/client/components/swimlanes/swimlanes.css
    +++ b/client/components/swimlanes/swimlanes.css
    @@ -10,9 +10,6 @@
       max-height: 100%;
       position: relative;
     }
    -.swimlane.js-lists.js-swimlane {
    -  min-height: 150px;
    -}
     .swimlane-header-menu .swimlane-header-collapse-down {
       font-size: 50%;
       color: #a6a6a6;
    @@ -57,14 +54,12 @@
       width: 100%;
       min-width: 100%;
       position: relative;
    -  overflow: visible;
    +  overflow: hidden;
       min-height: 33px;
    -  padding: 0;
    -  margin: 0;
     }
     .swimlane .swimlane-header-wrap .swimlane-header {
       font-size: 14px;
    -  padding: 0;
    +  padding: 5px 5px;
       font-weight: bold;
       min-height: 33px;
       width: 100%;
    @@ -76,109 +71,60 @@
       position: relative;
       z-index: 10;
       pointer-events: auto;
    -  display: flex;
    -  align-items: center;
    -  justify-content: center;
    -  line-height: 1.2;
     }
     .swimlane .swimlane-header-wrap .swimlane-header-menu {
       position: absolute;
    -  top: 0;
    -  left: 0;
    -  padding: 0;
    -  margin: 0;
    +  padding: 5px 5px;
       font-size: 22px;
    -  line-height: 1;
       z-index: 20;
       pointer-events: auto;
     }
    -.swimlane .swimlane-header-wrap .swimlane-header-menu .js-open-swimlane-menu {
    -  top: calc(50% + 6px);
    -  padding: 5px;
    -  display: inline-block;
    -  margin-left: 30px;
    -  color: #a6a6a6;
    -  vertical-align: middle;
    -  line-height: 1.2;
    -}
     @media print {
       .swimlane .swimlane-header-wrap .swimlane-header-menu {
         display: none;
       }
     }
     .swimlane .swimlane-header-wrap .swimlane-header-plus-icon {
    -  top: calc(50% + 6px);
    -  padding: 5px;
    -  margin-left: 20px;
    +  margin-left: 5px;
    +  padding-right: 20px;
       font-size: 22px;
    -  color: #a6a6a6;
     }
     .swimlane .swimlane-header-wrap .swimlane-header-menu-icon {
    -  top: calc(50% + 6px);
    -  padding-left: 5px;
    +  padding-right: 20px;
       font-size: 22px;
     }
     .swimlane .swimlane-header-wrap .swimlane-header-handle {
    -  position: relative;
    -  top: calc(50% + 2px);
    -  padding: 2px 5px;
    +  position: absolute;
    +  padding: 7px;
    +  top: 50%;
    +  left: 230px;
       font-size: clamp(16px, 3vw, 20px);
    -  display: inline-block;
    -  vertical-align: middle;
    -  margin-left: 30px;
    +  transform: translateY(-50%);
    +  display: flex;
    +  align-items: center;
    +  justify-content: center;
       cursor: move;
    +  z-index: 15;
       pointer-events: auto;
    -  color: #a6a6a6;
    -  line-height: 1.2;
     }
     .swimlane .swimlane-header-wrap .swimlane-header-miniscreen-handle {
    -  position: relative;
    -  padding: 2px 5px;
    -  top: calc(50% + 2px);
    +  position: absolute;
    +  padding: 7px;
    +  top: 50%;
    +  transform: translateY(-50%);
    +  right: 10px;
       font-size: 24px;
    -  display: inline-block;
    -  vertical-align: middle;
    -  margin-left: 30px;
       cursor: move;
    +  z-index: 15;
       pointer-events: auto;
    -  color: #a6a6a6;
     }
     
    -/* Swimlane collapse button styling - matches list collapse button */
    -.swimlane .swimlane-header-wrap .swimlane-header-menu .swimlane-collapse-indicator {
    -  color: #a6a6a6;
    -  display: inline-block;
    -  vertical-align: middle;
    -  padding: 5px;
    -  border: none;
    -  border-radius: 0;
    -  background-color: transparent;
    -  cursor: pointer;
    -  font-size: 18px;
    -  line-height: 1.2;
    -  text-align: center;
    -  text-decoration: none;
    -  margin: 0;
    -  flex-shrink: 0;
    +/* Safety: ensure wrapper is interactive and above list content */
    +.swimlane .swimlane-header-wrap {
    +  position: relative;
    +  z-index: 9;
    +  pointer-events: auto;
     }
    -.swimlane .swimlane-header-wrap .swimlane-header-menu .swimlane-collapse-indicator:hover {
    -  background-color: transparent;
    -  color: #333;
    -}
    -
    -.swimlane .swimlane-header-wrap .swimlane-header-menu .js-open-swimlane-menu:hover {
    -  color: #333;
    -}
    -
    -.swimlane .swimlane-header-wrap .swimlane-header-plus-icon:hover {
    -  color: #333;
    -}
    -
    -.swimlane .swimlane-header-wrap .swimlane-header-handle:hover,
    -.swimlane .swimlane-header-wrap .swimlane-header-miniscreen-handle:hover {
    -  color: #333;
    -}
    -
     #js-swimlane-height-edit .swimlane-height-error {
       display: none;
     }
    diff --git a/client/components/swimlanes/swimlanes.jade b/client/components/swimlanes/swimlanes.jade
    index 4f3ae4ed6..25e634573 100644
    --- a/client/components/swimlanes/swimlanes.jade
    +++ b/client/components/swimlanes/swimlanes.jade
    @@ -30,9 +30,15 @@ template(name="listsGroup")
           if currentList
             +list(currentList)
           else
    +        if currentUser.isBoardMember
    +          unless currentUser.isCommentOnly
    +            +addListForm
             each lists
               +miniList(this)
         else
    +      if currentUser.isBoardMember
    +        unless currentUser.isCommentOnly
    +          +addListForm
           each lists
             if visible this
               +list(this)
    @@ -42,9 +48,7 @@ template(name="listsGroup")
     template(name="addListForm")
       unless currentUser.isWorker
         unless currentUser.isCommentOnly
    -      unless currentUser.isReadOnly
    -        unless currentUser.isReadAssignedOnly
    -          .list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
    +      .list.list-composer.js-list-composer(class="{{#if isMiniScreen}}mini-list{{/if}}")
             .list-header-add
               +inlinedForm(autoclose=false)
                 input.list-name-input.full-line(type="text" placeholder="{{_ 'add-list'}}"
    @@ -57,7 +61,7 @@ template(name="addListForm")
                 .edit-controls.clearfix
                   button.primary.confirm(type="submit") {{_ 'save'}}
                   .js-close-inlined-form
    -                i.fa.fa-times-thin
    +                | ❌
                   unless currentBoard.isTemplatesBoard
                     unless currentBoard.isTemplateBoard
                       span.quiet
    @@ -65,7 +69,7 @@ template(name="addListForm")
                         a.js-list-template {{_ 'template'}}
               else
                 a.open-list-composer.js-open-inlined-form(title="{{_ 'add-list'}}")
    -              i.fa.fa-plus
    +              | ➕
     
     template(name="moveSwimlanePopup")
       if currentUser
    diff --git a/client/components/swimlanes/swimlanes.js b/client/components/swimlanes/swimlanes.js
    index 290df6293..e0dd896d5 100644
    --- a/client/components/swimlanes/swimlanes.js
    +++ b/client/components/swimlanes/swimlanes.js
    @@ -2,143 +2,7 @@ import { ReactiveCache } from '/imports/reactiveCache';
     import dragscroll from '@wekanteam/dragscroll';
     const { calculateIndex } = Utils;
     
    -function saveSorting(ui) {
    -  // To attribute the new index number, we need to get the DOM element
    -  // of the previous and the following list -- if any.
    -  const prevListDom = ui.item.prev('.js-list').get(0);
    -  const nextListDom = ui.item.next('.js-list').get(0);
    -  const sortIndex = calculateIndex(prevListDom, nextListDom, 1);
     
    -  const listDomElement = ui.item.get(0);
    -  if (!listDomElement) {
    -    return;
    -  }
    -
    -  let list;
    -  try {
    -    list = Blaze.getData(listDomElement);
    -  } catch (error) {
    -    return;
    -  }
    -
    -  if (!list) {
    -    return;
    -  }
    -
    -  // Detect if the list was dropped in a different swimlane
    -  const targetSwimlaneDom = ui.item.closest('.js-swimlane');
    -  let targetSwimlaneId = null;
    -
    -  if (targetSwimlaneDom.length > 0) {
    -    // List was dropped in a swimlane
    -    try {
    -      targetSwimlaneId = targetSwimlaneDom.attr('id').replace('swimlane-', '');
    -    } catch (error) {
    -      return;
    -    }
    -  } else {
    -    // List was dropped in lists view (not swimlanes view)
    -    // In this case, assign to the default swimlane
    -    const currentBoard = ReactiveCache.getBoard(Session.get('currentBoard'));
    -    if (currentBoard) {
    -      const defaultSwimlane = currentBoard.getDefaultSwimline();
    -      if (defaultSwimlane) {
    -        targetSwimlaneId = defaultSwimlane._id;
    -      }
    -    }
    -  }
    -
    -  // Get the original swimlane ID of the list (handle backward compatibility)
    -  const originalSwimlaneId = list.getEffectiveSwimlaneId ? list.getEffectiveSwimlaneId() : (list.swimlaneId || null);
    -
    -  // Prepare update object
    -  const updateData = {
    -    sort: sortIndex.base,
    -  };
    -
    -  // Check if the list was dropped in a different swimlane
    -  const isDifferentSwimlane = targetSwimlaneId && targetSwimlaneId !== originalSwimlaneId;
    -
    -  // If the list was dropped in a different swimlane, update the swimlaneId
    -  if (isDifferentSwimlane) {
    -    updateData.swimlaneId = targetSwimlaneId;
    -
    -    // Move all cards in the list to the new swimlane
    -    const cardsInList = ReactiveCache.getCards({
    -      listId: list._id,
    -      archived: false
    -    });
    -
    -    cardsInList.forEach(card => {
    -      card.move(list.boardId, targetSwimlaneId, list._id);
    -    });
    -
    -    // Don't cancel the sortable when moving to a different swimlane
    -    // The DOM move should be allowed to complete
    -  }
    -  // Allow reordering within the same swimlane by not canceling the sortable
    -
    -  // Do not update the restricted collection on the client; rely on the server method below.
    -
    -  // Save to localStorage for non-logged-in users (backup)
    -  if (!Meteor.userId()) {
    -    try {
    -      const boardId = list.boardId;
    -      const listId = list._id;
    -      const listOrderKey = `wekan-list-order-${boardId}`;
    -
    -      let listOrder = JSON.parse(localStorage.getItem(listOrderKey) || '{}');
    -      if (!listOrder.lists) listOrder.lists = [];
    -
    -      const listIndex = listOrder.lists.findIndex(l => l.id === listId);
    -      if (listIndex >= 0) {
    -        listOrder.lists[listIndex].sort = sortIndex.base;
    -        listOrder.lists[listIndex].swimlaneId = updateData.swimlaneId;
    -        listOrder.lists[listIndex].updatedAt = new Date().toISOString();
    -      } else {
    -        listOrder.lists.push({
    -          id: listId,
    -          sort: sortIndex.base,
    -          swimlaneId: updateData.swimlaneId
    -        });
    -      }
    -
    -      localStorage.setItem(listOrderKey, JSON.stringify(listOrder));
    -    } catch (e) {
    -    }
    -  }
    -
    -  // Persist to server
    -  Meteor.call('updateListSort', list._id, list.boardId, updateData, function (error) {
    -    if (error) {
    -      Meteor.subscribe('board', list.boardId, false);
    -    }
    -  });
    -
    -  // Try to get board component
    -  try {
    -    const boardBodyEl = ui.item[0]?.closest?.('.board-body') || document.querySelector('.board-body');
    -    const boardView = boardBodyEl && Blaze.getView(boardBodyEl, 'Template.boardBody');
    -    const boardComponent = boardView?.templateInstance?.();
    -    if (boardComponent && boardComponent.setIsDragging) {
    -      if (boardComponent) boardComponent.setIsDragging(false);
    -    }
    -  } catch (e) {
    -    // Silent fail
    -  }
    -
    -  // Re-enable dragscroll after list dragging is complete
    -  try {
    -    dragscroll.reset();
    -  } catch (e) {
    -    // Silent fail
    -  }
    -
    -  // Re-enable dragscroll on all swimlanes
    -  $('.js-swimlane').each(function () {
    -    $(this).addClass('dragscroll');
    -  });
    -}
     
     function currentListIsInThisSwimlane(swimlaneId) {
       const currentList = Utils.getCurrentList();
    @@ -181,60 +45,19 @@ function currentCardIsInThisList(listId, swimlaneId) {
       //          without using currentuser above, because currentuser is null.
     }
     
    -function syncListOrderFromStorage(boardId) {
    -  if (Meteor.userId()) {
    -    // Logged-in users: don't use localStorage, trust server
    -    return;
    -  }
    -
    -  try {
    -    const listOrderKey = `wekan-list-order-${boardId}`;
    -    const storageData = localStorage.getItem(listOrderKey);
    -
    -    if (!storageData) return;
    -
    -    const listOrder = JSON.parse(storageData);
    -    if (!listOrder.lists || listOrder.lists.length === 0) return;
    -
    -    // Compare each list's order in localStorage with database
    -    listOrder.lists.forEach(storedList => {
    -      const dbList = Lists.findOne(storedList.id);
    -      if (dbList) {
    -        // Check if localStorage has newer data (compare timestamps)
    -        const storageTime = new Date(storedList.updatedAt).getTime();
    -        const dbTime = new Date(dbList.modifiedAt).getTime();
    -
    -        // If storage is newer OR db is missing the field, use storage value
    -        if (storageTime > dbTime || dbList.sort !== storedList.sort) {
    -          console.debug(`Restoring list ${storedList.id} sort from localStorage (storage: ${storageTime}, db: ${dbTime})`);
    -
    -          // Update local minimongo first
    -          Lists.update(storedList.id, {
    -            $set: {
    -              sort: storedList.sort,
    -              swimlaneId: storedList.swimlaneId,
    -            },
    -          });
    -        }
    -      }
    -    });
    -  } catch (e) {
    -    console.warn('Failed to sync list order from localStorage:', e);
    -  }
    -};
    -
     function initSortable(boardComponent, $listsDom) {
       // Safety check: ensure we have valid DOM elements
       if (!$listsDom || $listsDom.length === 0) {
         console.error('initSortable: No valid DOM elements provided');
         return;
       }
    -
    +  
       // Check if sortable is already initialized
       if ($listsDom.data('uiSortable') || $listsDom.data('sortable')) {
         $listsDom.sortable('destroy');
       }
    -
    +  
    +  
       // We want to animate the card details window closing. We rely on CSS
       // transition for the actual animation.
       $listsDom._uihooks = {
    @@ -254,29 +77,29 @@ function initSortable(boardComponent, $listsDom) {
         },
       };
     
    -
    +  
       // Add click debugging for drag handles
       $listsDom.on('mousedown', '.js-list-handle', function(e) {
         e.stopPropagation();
       });
    -
    +  
       $listsDom.on('mousedown', '.js-list-header', function(e) {
       });
    -
    +  
       // Add debugging for any mousedown on lists
       $listsDom.on('mousedown', '.js-list', function(e) {
       });
    -
    +  
       // Add debugging for sortable events
       $listsDom.on('sortstart', function(e, ui) {
       });
    -
    +  
       $listsDom.on('sortbeforestop', function(e, ui) {
       });
    -
    +  
       $listsDom.on('sortstop', function(e, ui) {
       });
    -
    +  
       try {
         $listsDom.sortable({
           connectWith: '.js-swimlane, .js-lists',
    @@ -292,294 +115,219 @@ function initSortable(boardComponent, $listsDom) {
           distance: 3,
           forcePlaceholderSize: true,
           cursor: 'move',
    -      start(evt, ui) {
    -        ui.helper.css('z-index', 1000);
    -        ui.placeholder.height(ui.helper.height());
    -        ui.placeholder.width(ui.helper.width());
    -        EscapeActions.executeUpTo('popup-close');
    -        if (boardComponent) boardComponent.setIsDragging(true);
    -
    -        // Add visual feedback for list being dragged
    -        ui.item.addClass('ui-sortable-helper');
    -
    -        // Disable dragscroll during list dragging to prevent interference
    -        try {
    -          dragscroll.reset();
    -        } catch (e) {
    -        }
    -
    -        // Also disable dragscroll on all swimlanes during list dragging
    -        $('.js-swimlane').each(function() {
    -          $(this).removeClass('dragscroll');
    -        });
    -      },
    -      beforeStop(evt, ui) {
    -        // Clean up visual feedback
    -        ui.item.removeClass('ui-sortable-helper');
    -      },
    -      stop(evt, ui) {
    -        saveSorting(ui);
    +    start(evt, ui) {
    +      ui.helper.css('z-index', 1000);
    +      ui.placeholder.height(ui.helper.height());
    +      ui.placeholder.width(ui.helper.width());
    +      EscapeActions.executeUpTo('popup-close');
    +      boardComponent.setIsDragging(true);
    +      
    +      // Add visual feedback for list being dragged
    +      ui.item.addClass('ui-sortable-helper');
    +      
    +      // Disable dragscroll during list dragging to prevent interference
    +      try {
    +        dragscroll.reset();
    +      } catch (e) {
           }
    -    });
    +      
    +      // Also disable dragscroll on all swimlanes during list dragging
    +      $('.js-swimlane').each(function() {
    +        $(this).removeClass('dragscroll');
    +      });
    +    },
    +    beforeStop(evt, ui) {
    +      // Clean up visual feedback
    +      ui.item.removeClass('ui-sortable-helper');
    +    },
    +    stop(evt, ui) {
    +      // To attribute the new index number, we need to get the DOM element
    +      // of the previous and the following card -- if any.
    +      const prevListDom = ui.item.prev('.js-list').get(0);
    +      const nextListDom = ui.item.next('.js-list').get(0);
    +      const sortIndex = calculateIndex(prevListDom, nextListDom, 1);
    +
    +      const listDomElement = ui.item.get(0);
    +      if (!listDomElement) {
    +        console.error('List DOM element not found during drag stop');
    +        return;
    +      }
    +      
    +      let list;
    +      try {
    +        list = Blaze.getData(listDomElement);
    +      } catch (error) {
    +        console.error('Error getting list data:', error);
    +        return;
    +      }
    +      
    +      if (!list) {
    +        console.error('List data not found for element:', listDomElement);
    +        return;
    +      }
    +
    +      // Detect if the list was dropped in a different swimlane
    +      const targetSwimlaneDom = ui.item.closest('.js-swimlane');
    +      let targetSwimlaneId = null;
    +
    +
    +      if (targetSwimlaneDom.length > 0) {
    +        // List was dropped in a swimlane
    +        try {
    +          targetSwimlaneId = targetSwimlaneDom.attr('id').replace('swimlane-', '');
    +        } catch (error) {
    +          console.error('Error getting target swimlane ID:', error);
    +          return;
    +        }
    +      } else {
    +        // List was dropped in lists view (not swimlanes view)
    +        // In this case, assign to the default swimlane
    +        const currentBoard = ReactiveCache.getBoard(Session.get('currentBoard'));
    +        if (currentBoard) {
    +          const defaultSwimlane = currentBoard.getDefaultSwimline();
    +          if (defaultSwimlane) {
    +            targetSwimlaneId = defaultSwimlane._id;
    +          }
    +        }
    +      }
    +
    +      // Get the original swimlane ID of the list (handle backward compatibility)
    +      const originalSwimlaneId = list.getEffectiveSwimlaneId ? list.getEffectiveSwimlaneId() : (list.swimlaneId || null);
    +
    +      /*
    +            Reverted incomplete change list width,
    +            removed from below Lists.update:
    +             https://github.com/wekan/wekan/issues/4558
    +                $set: {
    +                  width: list._id.width(),
    +                  height: list._id.height(),
    +      */
    +
    +      // Prepare update object
    +      const updateData = {
    +        sort: sortIndex.base,
    +      };
    +
    +      // Check if the list was dropped in a different swimlane
    +      const isDifferentSwimlane = targetSwimlaneId && targetSwimlaneId !== originalSwimlaneId;
    +
    +      // If the list was dropped in a different swimlane, update the swimlaneId
    +      if (isDifferentSwimlane) {
    +        updateData.swimlaneId = targetSwimlaneId;
    +
    +        // Move all cards in the list to the new swimlane
    +        const cardsInList = ReactiveCache.getCards({
    +          listId: list._id,
    +          archived: false
    +        });
    +
    +        cardsInList.forEach(card => {
    +          card.move(list.boardId, targetSwimlaneId, list._id);
    +        });
    +
    +
    +        // Don't cancel the sortable when moving to a different swimlane
    +        // The DOM move should be allowed to complete
    +      }
    +      // Allow reordering within the same swimlane by not canceling the sortable
    +
    +      try {
    +        Lists.update(list._id, {
    +          $set: updateData,
    +        });
    +      } catch (error) {
    +        console.error('Error updating list:', error);
    +        return;
    +      }
    +
    +      boardComponent.setIsDragging(false);
    +      
    +      // Re-enable dragscroll after list dragging is complete
    +      try {
    +        dragscroll.reset();
    +      } catch (e) {
    +      }
    +      
    +      // Re-enable dragscroll on all swimlanes
    +      $('.js-swimlane').each(function() {
    +        $(this).addClass('dragscroll');
    +      });
    +    },
    +  });
       } catch (error) {
         console.error('Error initializing list sortable:', error);
         return;
       }
    -
    -
    +  
    +  
       // Check if drag handles exist
       const dragHandles = $listsDom.find('.js-list-handle');
    -
    +  
       // Check if lists exist
       const lists = $listsDom.find('.js-list');
     
       // Skip the complex autorun and options for now
     }
     
    -Template.swimlane.onCreated(function () {
    -  this.draggingActive = new ReactiveVar(false);
    -  this._isDragging = false;
    -  this._lastDragPositionX = 0;
    -});
    +BlazeComponent.extendComponent({
    +  onRendered() {
    +    const boardComponent = this.parentComponent();
    +    const $listsDom = this.$('.js-lists');
    +    
     
    -Template.swimlane.onRendered(function () {
    -  const tpl = this;
    -  const boardBodyEl = tpl.firstNode?.parentElement?.closest?.('.board-body') || document.querySelector('.board-body');
    -  const boardView = boardBodyEl && Blaze.getView(boardBodyEl, 'Template.boardBody');
    -  const boardComponent = boardView?.templateInstance?.();
    -  const $listsDom = tpl.$('.js-lists');
    -  // Sync list order from localStorage on board load
    -  const boardId = Session.get('currentBoard');
    -  if (boardId) {
    -    // Small delay to allow pubsub to settle
    -    Meteor.setTimeout(() => {
    -      syncListOrderFromStorage(boardId);
    -    }, 500);
    -  }
    -
    -  if (!Utils.getCurrentCardId() && boardComponent) {
    -    boardComponent.scrollLeft();
    -  }
    -
    -  // Try a simpler approach - initialize sortable directly like cards do
    -  initializeSwimlaneResize(tpl);
    -
    -  // Wait for DOM to be ready
    -  setTimeout(() => {
    -    const handleSelector = Utils.isTouchScreenOrShowDesktopDragHandles()
    -      ? '.js-list-handle'
    -      : '.js-list-header';
    -    const $parent = tpl.$('.js-lists');
    -
    -    if ($parent.length > 0) {
    -
    -      // Check for drag handles
    -      const $handles = $parent.find('.js-list-handle');
    -
    -      // Test if drag handles are clickable
    -      $handles.on('click', function (e) {
    -        e.preventDefault();
    -        e.stopPropagation();
    -      });
    -
    -      $parent.sortable({
    -        connectWith: '.js-swimlane, .js-lists',
    -        tolerance: 'pointer',
    -        appendTo: '.board-canvas',
    -        helper: 'clone',
    -        items: '.js-list:not(.js-list-composer)',
    -        placeholder: 'list placeholder',
    -        distance: 7,
    -        handle: handleSelector,
    -        disabled: !Utils.canModifyBoard(),
    -        dropOnEmpty: true,
    -        start(evt, ui) {
    -          ui.helper.css('z-index', 1000);
    -          ui.placeholder.height(ui.helper.height());
    -          ui.placeholder.width(ui.helper.width());
    -          EscapeActions.executeUpTo('popup-close');
    -          if (boardComponent) boardComponent.setIsDragging(true);
    -        },
    -        stop(evt, ui) {
    -          if (boardComponent) boardComponent.setIsDragging(false);
    -          saveSorting(ui);
    -        },
    -      });
    -      // Reactively update handle when user toggles desktop drag handles
    -      tpl.autorun(() => {
    -        const newHandle = Utils.isTouchScreenOrShowDesktopDragHandles()
    -          ? '.js-list-handle'
    -          : '.js-list-header';
    -        if ($parent.data('uiSortable') || $parent.data('sortable')) {
    -          try {
    -            $parent.sortable('option', 'handle', newHandle);
    -          } catch (e) {}
    -        }
    -      });
    +    if (!Utils.getCurrentCardId()) {
    +      boardComponent.scrollLeft();
         }
    -  }, 100);
    -});
     
    -function initializeSwimlaneResize(tpl) {
    -  // Check if we're still in a valid template context
    -  if (!Template.currentData()) {
    -    console.warn('No current template data available for swimlane resize initialization');
    -    return;
    -  }
    -
    -  const swimlane = Template.currentData();
    -  const $swimlane = $(`#swimlane-${swimlane._id}`);
    -  const $resizeHandle = $swimlane.find('.js-swimlane-resize-handle');
    -
    -  // Check if elements exist
    -  if (!$swimlane.length || !$resizeHandle.length) {
    -    console.warn('Swimlane or resize handle not found, retrying in 100ms');
    -    Meteor.setTimeout(() => {
    -      if (!tpl.isDestroyed) {
    -        initializeSwimlaneResize(tpl);
    +    // Try a simpler approach - initialize sortable directly like cards do
    +    
    +    // Wait for DOM to be ready
    +    setTimeout(() => {
    +      const $lists = this.$('.js-list');
    +      
    +      const $parent = $lists.parent();
    +      
    +      if ($lists.length > 0) {
    +        
    +        // Check for drag handles
    +        const $handles = $parent.find('.js-list-handle');
    +        
    +        // Test if drag handles are clickable
    +        $handles.on('click', function(e) {
    +          e.preventDefault();
    +          e.stopPropagation();
    +        });
    +        
    +        $parent.sortable({
    +          connectWith: '.js-swimlane, .js-lists',
    +          tolerance: 'pointer',
    +          appendTo: '.board-canvas',
    +          helper: 'clone',
    +          items: '.js-list:not(.js-list-composer)',
    +          placeholder: 'list placeholder',
    +          distance: 7,
    +          handle: '.js-list-handle',
    +          disabled: !Utils.canModifyBoard(),
    +          start(evt, ui) {
    +            ui.helper.css('z-index', 1000);
    +            ui.placeholder.height(ui.helper.height());
    +            ui.placeholder.width(ui.helper.width());
    +            EscapeActions.executeUpTo('popup-close');
    +            boardComponent.setIsDragging(true);
    +          },
    +          stop(evt, ui) {
    +            boardComponent.setIsDragging(false);
    +          }
    +        });
    +      } else {
           }
         }, 100);
    -    return;
    -  }
    -
    -  if ($resizeHandle.length === 0) {
    -    return;
    -  }
    -
    -  let isResizing = false;
    -  let startY = 0;
    -  let startHeight = 0;
    -  const minHeight = 100;
    -  const maxHeight = 2000;
    -
    -  const startResize = (e) => {
    -    isResizing = true;
    -    startY = e.pageY || e.originalEvent.touches[0].pageY;
    -    startHeight = parseInt($swimlane.css('height')) || 300;
    -
    -    $swimlane.addClass('swimlane-resizing');
    -    $('body').addClass('swimlane-resizing-active');
    -    $('body').css('user-select', 'none');
    -
    -    e.preventDefault();
    -    e.stopPropagation();
    -  };
    -
    -  const doResize = (e) => {
    -    if (!isResizing) {
    -      return;
    -    }
    -
    -    const currentY = e.pageY || e.originalEvent.touches[0].pageY;
    -    const deltaY = currentY - startY;
    -    const newHeight = Math.max(minHeight, Math.min(maxHeight, startHeight + deltaY));
    -
    -    // Apply the new height immediately for real-time feedback
    -    $swimlane[0].style.setProperty('--swimlane-height', `${newHeight}px`);
    -    $swimlane[0].style.setProperty('height', `${newHeight}px`);
    -    $swimlane[0].style.setProperty('min-height', `${newHeight}px`);
    -    $swimlane[0].style.setProperty('max-height', `${newHeight}px`);
    -    $swimlane[0].style.setProperty('flex', 'none');
    -    $swimlane[0].style.setProperty('flex-basis', 'auto');
    -    $swimlane[0].style.setProperty('flex-grow', '0');
    -    $swimlane[0].style.setProperty('flex-shrink', '0');
    -
    -    e.preventDefault();
    -    e.stopPropagation();
    -  };
    -
    -  const stopResize = (e) => {
    -    if (!isResizing) return;
    -
    -    isResizing = false;
    -
    -    // Calculate final height
    -    const currentY = e.pageY || e.originalEvent.touches[0].pageY;
    -    const deltaY = currentY - startY;
    -    const finalHeight = Math.max(minHeight, Math.min(maxHeight, startHeight + deltaY));
    -
    -    // Ensure the final height is applied
    -    $swimlane[0].style.setProperty('--swimlane-height', `${finalHeight}px`);
    -    $swimlane[0].style.setProperty('height', `${finalHeight}px`);
    -    $swimlane[0].style.setProperty('min-height', `${finalHeight}px`);
    -    $swimlane[0].style.setProperty('max-height', `${finalHeight}px`);
    -    $swimlane[0].style.setProperty('flex', 'none');
    -    $swimlane[0].style.setProperty('flex-basis', 'auto');
    -    $swimlane[0].style.setProperty('flex-grow', '0');
    -    $swimlane[0].style.setProperty('flex-shrink', '0');
    -
    -    // Remove visual feedback but keep the height
    -    $swimlane.removeClass('swimlane-resizing');
    -    $('body').removeClass('swimlane-resizing-active');
    -    $('body').css('user-select', '');
    -
    -    // Save the new height using the existing system
    -    const boardId = swimlane.boardId;
    -    const swimlaneId = swimlane._id;
    -
    -    if (process.env.DEBUG === 'true') {
    -    }
    -
    -    const currentUser = ReactiveCache.getCurrentUser();
    -    if (currentUser) {
    -      // For logged-in users, use server method
    -      Meteor.call('applySwimlaneHeightToStorage', boardId, swimlaneId, finalHeight, (error, result) => {
    -        if (error) {
    -          console.error('Error saving swimlane height:', error);
    -        } else {
    -          if (process.env.DEBUG === 'true') {
    -          }
    -        }
    -      });
    -    } else {
    -      // For non-logged-in users, save to localStorage directly
    -      try {
    -        const stored = localStorage.getItem('wekan-swimlane-heights');
    -        let heights = stored ? JSON.parse(stored) : {};
    -
    -        if (!heights[boardId]) {
    -          heights[boardId] = {};
    -        }
    -        heights[boardId][swimlaneId] = finalHeight;
    -
    -        localStorage.setItem('wekan-swimlane-heights', JSON.stringify(heights));
    -
    -        if (process.env.DEBUG === 'true') {
    -        }
    -      } catch (e) {
    -        console.warn('Error saving swimlane height to localStorage:', e);
    -      }
    -    }
    -
    -    e.preventDefault();
    -  };
    -
    -  // Mouse events
    -  $resizeHandle.on('mousedown', startResize);
    -  $(document).on('mousemove', doResize);
    -  $(document).on('mouseup', stopResize);
    -
    -  // Touch events for mobile
    -  $resizeHandle.on('touchstart', startResize, { passive: false });
    -  $(document).on('touchmove', doResize, { passive: false });
    -  $(document).on('touchend', stopResize, { passive: false });
    -
    -  // Prevent dragscroll interference
    -  $resizeHandle.on('mousedown', (e) => {
    -    e.stopPropagation();
    -  });
    -}
    -
    -Template.swimlane.helpers({
    -  canSeeAddList() {
    -    return ReactiveCache.getCurrentUser().isBoardAdmin();
       },
    -  lists() {
    -    // Return per-swimlane lists for this swimlane
    -    return this.myLists();
    -  },
    -  collapseSwimlane() {
    -    return Utils.getSwimlaneCollapseState(this);
    +  onCreated() {
    +    this.draggingActive = new ReactiveVar(false);
    +
    +    this._isDragging = false;
    +    this._lastDragPositionX = 0;
       },
       id() {
         return this._id;
    @@ -592,6 +340,7 @@ Template.swimlane.helpers({
       },
       visible(list) {
         if (list.archived) {
    +      // Show archived list only when filter archive is on
           if (!Filter.archive.isSelected()) {
             return false;
           }
    @@ -602,6 +351,8 @@ Template.swimlane.helpers({
           }
         }
         if (Filter.hideEmpty.isSelected()) {
    +      // Check for cards in all swimlanes, not just the current one
    +      // This ensures lists with cards in other swimlanes are still visible
           const cards = list.cards();
           if (cards.length === 0) {
             return false;
    @@ -609,14 +360,74 @@ Template.swimlane.helpers({
         }
         return true;
       },
    +  events() {
    +    return [
    +      {
    +        // Click-and-drag action
    +        'mousedown .board-canvas'(evt) {
    +          // Translating the board canvas using the click-and-drag action can
    +          // conflict with the build-in browser mechanism to select text. We
    +          // define a list of elements in which we disable the dragging because
    +          // the user will legitimately expect to be able to select some text with
    +          // his mouse.
    +
    +          const noDragInside = ['a', 'input', 'textarea', 'p'].concat(
    +            Utils.isTouchScreenOrShowDesktopDragHandles()
    +              ? ['.js-list-handle', '.js-swimlane-header-handle']
    +              : ['.js-list-header'],
    +          ).concat([
    +            '.js-list-resize-handle',
    +            '.js-swimlane-resize-handle'
    +          ]);
    +
    +          const isResizeHandle = $(evt.target).closest('.js-list-resize-handle, .js-swimlane-resize-handle').length > 0;
    +          const isInNoDragArea = $(evt.target).closest(noDragInside.join(',')).length > 0;
    +          
    +          if (isResizeHandle) {
    +            return;
    +          }
    +          
    +          if (
    +            !isInNoDragArea &&
    +            this.$('.swimlane').prop('clientHeight') > evt.offsetY
    +          ) {
    +            this._isDragging = true;
    +            this._lastDragPositionX = evt.clientX;
    +          }
    +        },
    +        mouseup() {
    +          if (this._isDragging) {
    +            this._isDragging = false;
    +          }
    +        },
    +        mousemove(evt) {
    +          if (this._isDragging) {
    +            // Update the canvas position
    +            this.listsDom.scrollLeft -= evt.clientX - this._lastDragPositionX;
    +            this._lastDragPositionX = evt.clientX;
    +            // Disable browser text selection while dragging
    +            evt.stopPropagation();
    +            evt.preventDefault();
    +            // Don't close opened card or inlined form at the end of the
    +            // click-and-drag.
    +            EscapeActions.executeUpTo('popup-close');
    +            EscapeActions.preventNextClick();
    +          }
    +        },
    +      },
    +    ];
    +  },
    +
       swimlaneHeight() {
         const user = ReactiveCache.getCurrentUser();
         const swimlane = Template.currentData();
    -
    +    
         let height;
         if (user) {
    +      // For logged-in users, get from user profile
           height = user.getSwimlaneHeightFromStorage(swimlane.boardId, swimlane._id);
         } else {
    +      // For non-logged-in users, get from localStorage
           try {
             const stored = localStorage.getItem('wekan-swimlane-heights');
             if (stored) {
    @@ -634,129 +445,261 @@ Template.swimlane.helpers({
             height = -1;
           }
         }
    -
    +    
         return height == -1 ? "auto" : (height + 5 + "px");
       },
    -});
     
    -Template.swimlane.events({
    -  // Click-and-drag action
    -  'mousedown .board-canvas'(evt, tpl) {
    -    const noDragInside = ['a', 'input', 'textarea', 'p'].concat(
    -      Utils.isTouchScreenOrShowDesktopDragHandles()
    -        ? ['.js-list-handle', '.js-swimlane-header-handle']
    -        : ['.js-list-header'],
    -    ).concat([
    -      '.js-list-resize-handle',
    -      '.js-swimlane-resize-handle',
    -    ]);
    +  onRendered() {
    +    // Initialize swimlane resize functionality immediately
    +    this.initializeSwimlaneResize();
    +  },
     
    -    const isResizeHandle = $(evt.target).closest('.js-list-resize-handle, .js-swimlane-resize-handle').length > 0;
    -    const isInNoDragArea = $(evt.target).closest(noDragInside.join(',')).length > 0;
    -
    -    if (isResizeHandle) {
    +  initializeSwimlaneResize() {
    +    // Check if we're still in a valid template context
    +    if (!Template.currentData()) {
    +      console.warn('No current template data available for swimlane resize initialization');
    +      return;
    +    }
    +    
    +    const swimlane = Template.currentData();
    +    const $swimlane = $(`#swimlane-${swimlane._id}`);
    +    const $resizeHandle = $swimlane.find('.js-swimlane-resize-handle');
    +    
    +    // Check if elements exist
    +    if (!$swimlane.length || !$resizeHandle.length) {
    +      console.warn('Swimlane or resize handle not found, retrying in 100ms');
    +      Meteor.setTimeout(() => {
    +        if (!this.isDestroyed) {
    +          this.initializeSwimlaneResize();
    +        }
    +      }, 100);
    +      return;
    +    }
    +    
    +    
    +    if ($resizeHandle.length === 0) {
           return;
         }
     
    -    if (
    -      !isInNoDragArea &&
    -      tpl.$('.swimlane').prop('clientHeight') > evt.offsetY
    -    ) {
    -      tpl._isDragging = true;
    -      tpl._lastDragPositionX = evt.clientX;
    -    }
    -  },
    -  mouseup(evt, tpl) {
    -    if (tpl._isDragging) {
    -      tpl._isDragging = false;
    -    }
    -  },
    -  mousemove(evt, tpl) {
    -    if (tpl._isDragging) {
    -      // Update the canvas position
    -      tpl.listsDom.scrollLeft -= evt.clientX - tpl._lastDragPositionX;
    -      tpl._lastDragPositionX = evt.clientX;
    -      // Disable browser text selection while dragging
    -      evt.stopPropagation();
    -      evt.preventDefault();
    -      // Don't close opened card or inlined form at the end of the
    -      // click-and-drag.
    -      EscapeActions.executeUpTo('popup-close');
    -      EscapeActions.preventNextClick();
    -    }
    -  },
    -});
    +    let isResizing = false;
    +    let startY = 0;
    +    let startHeight = 0;
    +    const minHeight = 100;
    +    const maxHeight = 2000;
     
    +    const startResize = (e) => {
    +      isResizing = true;
    +      startY = e.pageY || e.originalEvent.touches[0].pageY;
    +      startHeight = parseInt($swimlane.css('height')) || 300;
    +      
    +      
    +      $swimlane.addClass('swimlane-resizing');
    +      $('body').addClass('swimlane-resizing-active');
    +      $('body').css('user-select', 'none');
    +      
    +      
    +      e.preventDefault();
    +      e.stopPropagation();
    +    };
     
    -Template.addListForm.onCreated(function () {
    -  this.currentBoard = Utils.getCurrentBoard();
    -  this.isListTemplatesSwimlane =
    -    this.currentBoard.isTemplatesBoard() &&
    -    Template.currentData().isListTemplatesSwimlane();
    -  this.currentSwimlane = Template.currentData();
    -});
    -
    -Template.addListForm.events({
    -  submit(evt, tpl) {
    -    evt.preventDefault();
    -
    -    const titleInput = tpl.find('.list-name-input');
    -    const title = titleInput?.value.trim();
    -
    -    if (!title) return;
    -
    -    let sortIndex = 0;
    -    const lastList = tpl.currentBoard.getLastList();
    -    const boardId = Utils.getCurrentBoardId();
    -    let swimlaneId = tpl.currentSwimlane._id;
    -
    -    const positionInput = tpl.find('.list-position-input');
    -
    -    if (positionInput) {
    -      const positionId = positionInput.value.trim();
    -      const selectedList = ReactiveCache.getList({ boardId, _id: positionId, archived: false });
    -
    -      if (selectedList) {
    -        sortIndex = selectedList.sort + 1;
    -        // Use the swimlane ID from the selected list to ensure the new list
    -        // is added to the same swimlane as the selected list
    -        swimlaneId = selectedList.swimlaneId;
    -      } else {
    -        sortIndex = Utils.calculateIndexData(lastList, null).base;
    +    const doResize = (e) => {
    +      if (!isResizing) {
    +        return;
           }
    -    } else {
    -      sortIndex = Utils.calculateIndexData(lastList, null).base;
    -    }
    +      
    +      const currentY = e.pageY || e.originalEvent.touches[0].pageY;
    +      const deltaY = currentY - startY;
    +      const newHeight = Math.max(minHeight, Math.min(maxHeight, startHeight + deltaY));
    +      
    +      
    +      // Apply the new height immediately for real-time feedback
    +      $swimlane[0].style.setProperty('--swimlane-height', `${newHeight}px`);
    +      $swimlane[0].style.setProperty('height', `${newHeight}px`);
    +      $swimlane[0].style.setProperty('min-height', `${newHeight}px`);
    +      $swimlane[0].style.setProperty('max-height', `${newHeight}px`);
    +      $swimlane[0].style.setProperty('flex', 'none');
    +      $swimlane[0].style.setProperty('flex-basis', 'auto');
    +      $swimlane[0].style.setProperty('flex-grow', '0');
    +      $swimlane[0].style.setProperty('flex-shrink', '0');
    +      
    +      
    +      e.preventDefault();
    +      e.stopPropagation();
    +    };
     
    -    Lists.insert({
    -      title,
    -      boardId: Session.get('currentBoard'),
    -      sort: sortIndex,
    -      type: tpl.isListTemplatesSwimlane ? 'template-list' : 'list',
    -      swimlaneId: swimlaneId, // Always set swimlaneId for per-swimlane list titles
    +    const stopResize = (e) => {
    +      if (!isResizing) return;
    +      
    +      isResizing = false;
    +      
    +      // Calculate final height
    +      const currentY = e.pageY || e.originalEvent.touches[0].pageY;
    +      const deltaY = currentY - startY;
    +      const finalHeight = Math.max(minHeight, Math.min(maxHeight, startHeight + deltaY));
    +      
    +      // Ensure the final height is applied
    +      $swimlane[0].style.setProperty('--swimlane-height', `${finalHeight}px`);
    +      $swimlane[0].style.setProperty('height', `${finalHeight}px`);
    +      $swimlane[0].style.setProperty('min-height', `${finalHeight}px`);
    +      $swimlane[0].style.setProperty('max-height', `${finalHeight}px`);
    +      $swimlane[0].style.setProperty('flex', 'none');
    +      $swimlane[0].style.setProperty('flex-basis', 'auto');
    +      $swimlane[0].style.setProperty('flex-grow', '0');
    +      $swimlane[0].style.setProperty('flex-shrink', '0');
    +      
    +      // Remove visual feedback but keep the height
    +      $swimlane.removeClass('swimlane-resizing');
    +      $('body').removeClass('swimlane-resizing-active');
    +      $('body').css('user-select', '');
    +      
    +      // Save the new height using the existing system
    +      const boardId = swimlane.boardId;
    +      const swimlaneId = swimlane._id;
    +      
    +      if (process.env.DEBUG === 'true') {
    +      }
    +      
    +      const currentUser = ReactiveCache.getCurrentUser();
    +      if (currentUser) {
    +        // For logged-in users, use server method
    +        Meteor.call('applySwimlaneHeightToStorage', boardId, swimlaneId, finalHeight, (error, result) => {
    +          if (error) {
    +            console.error('Error saving swimlane height:', error);
    +          } else {
    +            if (process.env.DEBUG === 'true') {
    +            }
    +          }
    +        });
    +      } else {
    +        // For non-logged-in users, save to localStorage directly
    +        try {
    +          const stored = localStorage.getItem('wekan-swimlane-heights');
    +          let heights = stored ? JSON.parse(stored) : {};
    +          
    +          if (!heights[boardId]) {
    +            heights[boardId] = {};
    +          }
    +          heights[boardId][swimlaneId] = finalHeight;
    +          
    +          localStorage.setItem('wekan-swimlane-heights', JSON.stringify(heights));
    +          
    +          if (process.env.DEBUG === 'true') {
    +          }
    +        } catch (e) {
    +          console.warn('Error saving swimlane height to localStorage:', e);
    +        }
    +      }
    +      
    +      e.preventDefault();
    +    };
    +
    +    // Mouse events
    +    $resizeHandle.on('mousedown', startResize);
    +    $(document).on('mousemove', doResize);
    +    $(document).on('mouseup', stopResize);
    +    
    +    // Touch events for mobile
    +    $resizeHandle.on('touchstart', startResize, { passive: false });
    +    $(document).on('touchmove', doResize, { passive: false });
    +    $(document).on('touchend', stopResize, { passive: false });
    +    
    +    
    +    // Prevent dragscroll interference
    +    $resizeHandle.on('mousedown', (e) => {
    +      e.stopPropagation();
         });
    -
    -    titleInput.value = '';
    -    titleInput.focus();
    +    
       },
    -  'click .js-list-template': Popup.open('searchElement'),
    +}).register('swimlane');
    +
    +
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.currentBoard = Utils.getCurrentBoard();
    +    this.isListTemplatesSwimlane =
    +      this.currentBoard.isTemplatesBoard() &&
    +      this.currentData().isListTemplatesSwimlane();
    +    this.currentSwimlane = this.currentData();
    +  },
    +
    +  // Proxy
    +  open() {
    +    this.childComponents('inlinedForm')[0].open();
    +  },
    +
    +  events() {
    +    return [
    +      {
    +        submit(evt) {
    +            evt.preventDefault();
    +
    +            const titleInput = this.find('.list-name-input');
    +            const title = titleInput?.value.trim();
    +
    +            if (!title) return;
    +
    +            let sortIndex = 0;
    +            const lastList = this.currentBoard.getLastList();
    +            const boardId = Utils.getCurrentBoardId();
    +
    +            const positionInput = this.find('.list-position-input');
    +
    +            if (positionInput) {
    +              const positionId = positionInput.value.trim();
    +              const selectedList = ReactiveCache.getList({ boardId, _id: positionId, archived: false });
    +
    +              if (selectedList) {
    +                sortIndex = selectedList.sort + 1;
    +              } else {
    +                sortIndex = Utils.calculateIndexData(lastList, null).base;
    +              }
    +            } else {
    +              sortIndex = Utils.calculateIndexData(lastList, null).base;
    +            }
    +
    +            Lists.insert({
    +              title,
    +              boardId: Session.get('currentBoard'),
    +              sort: sortIndex,
    +              type: this.isListTemplatesSwimlane ? 'template-list' : 'list',
    +              swimlaneId: this.currentSwimlane._id, // Always set swimlaneId for per-swimlane list titles
    +            });
    +
    +            titleInput.value = '';
    +            titleInput.focus();
    +        }
    +      },
    +      {
    +        'click .js-list-template': Popup.open('searchElement'),
    +      },
    +    ];
    +  },
    +}).register('addListForm');
    +
    +Template.swimlane.helpers({
    +  canSeeAddList() {
    +    return ReactiveCache.getCurrentUser().isBoardAdmin();
    +  },
    +  
    +  lists() {
    +    // Return per-swimlane lists for this swimlane
    +    return this.myLists();
    +  }
     });
     
     // Initialize sortable on DOM elements
     setTimeout(() => {
    +  const $swimlaneElements = $('.swimlane');
       const $listsGroupElements = $('.list-group');
    -  const computeHandle = () => (
    -    Utils.isTouchScreenOrShowDesktopDragHandles() ? '.js-list-handle' : '.js-list-header'
    -  );
    -
    -  // Initialize sortable on ALL listsGroup elements (even empty ones)
    -  $listsGroupElements.each(function(index) {
    -    const $listsGroup = $(this);
    -    const $lists = $listsGroup.find('.js-list');
    -
    -    // Only initialize on listsGroup elements that have the .js-lists class
    -    if ($listsGroup.hasClass('js-lists')) {
    -      $listsGroup.sortable({
    +  
    +  // Initialize sortable on ALL swimlane elements (even empty ones)
    +  $swimlaneElements.each(function(index) {
    +    const $swimlane = $(this);
    +    const $lists = $swimlane.find('.js-list');
    +    
    +    // Only initialize on swimlanes that have the .js-lists class (the container for lists)
    +    if ($swimlane.hasClass('js-lists')) {
    +      $swimlane.sortable({
             connectWith: '.js-swimlane, .js-lists',
             tolerance: 'pointer',
             appendTo: '.board-canvas',
    @@ -764,9 +707,8 @@ setTimeout(() => {
             items: '.js-list:not(.js-list-composer)',
             placeholder: 'list placeholder',
             distance: 7,
    -        handle: computeHandle(),
    +        handle: '.js-list-handle',
             disabled: !Utils.canModifyBoard(),
    -        dropOnEmpty: true,
             start(evt, ui) {
               ui.helper.css('z-index', 1000);
               ui.placeholder.height(ui.helper.height());
    @@ -774,11 +716,9 @@ setTimeout(() => {
               EscapeActions.executeUpTo('popup-close');
               // Try to get board component
               try {
    -            const bbEl = ui.item[0]?.closest?.('.board-body') || document.querySelector('.board-body');
    -            const bbView = bbEl && Blaze.getView(bbEl, 'Template.boardBody');
    -            const boardComponent = bbView?.templateInstance?.();
    +            const boardComponent = BlazeComponent.getComponentForElement(ui.item[0]);
                 if (boardComponent && boardComponent.setIsDragging) {
    -              if (boardComponent) boardComponent.setIsDragging(true);
    +              boardComponent.setIsDragging(true);
                 }
               } catch (e) {
                 // Silent fail
    @@ -795,14 +735,14 @@ setTimeout(() => {
               if (!listDomElement) {
                 return;
               }
    -
    +          
               let list;
               try {
                 list = Blaze.getData(listDomElement);
               } catch (error) {
                 return;
               }
    -
    +          
               if (!list) {
                 return;
               }
    @@ -860,74 +800,177 @@ setTimeout(() => {
               }
               // Allow reordering within the same swimlane by not canceling the sortable
     
    -          // Do not update the restricted collection on the client; rely on the server method below.
    -
    -          // Save to localStorage for non-logged-in users (backup)
    -          if (!Meteor.userId()) {
    -            try {
    -              const boardId = list.boardId;
    -              const listId = list._id;
    -              const listOrderKey = `wekan-list-order-${boardId}`;
    -
    -              let listOrder = JSON.parse(localStorage.getItem(listOrderKey) || '{}');
    -              if (!listOrder.lists) listOrder.lists = [];
    -
    -              const listIndex = listOrder.lists.findIndex(l => l.id === listId);
    -              if (listIndex >= 0) {
    -                listOrder.lists[listIndex].sort = sortIndex.base;
    -                listOrder.lists[listIndex].swimlaneId = updateData.swimlaneId;
    -                listOrder.lists[listIndex].updatedAt = new Date().toISOString();
    -              } else {
    -                listOrder.lists.push({
    -                  id: listId,
    -                  sort: sortIndex.base,
    -                  swimlaneId: updateData.swimlaneId,
    -                  updatedAt: new Date().toISOString()
    -                });
    -              }
    -
    -              localStorage.setItem(listOrderKey, JSON.stringify(listOrder));
    -            } catch (e) {
    -            }
    +          try {
    +            Lists.update(list._id, {
    +              $set: updateData,
    +            });
    +          } catch (error) {
    +            return;
               }
     
    -          // Persist to server
    -          Meteor.call('updateListSort', list._id, list.boardId, updateData, function(error) {
    -            if (error) {
    -              Meteor.subscribe('board', list.boardId, false);
    -            }
    -          });
    -
               // Try to get board component
               try {
    -            const bbEl2 = ui.item[0]?.closest?.('.board-body') || document.querySelector('.board-body');
    -            const bbView2 = bbEl2 && Blaze.getView(bbEl2, 'Template.boardBody');
    -            const boardComponent = bbView2?.templateInstance?.();
    +            const boardComponent = BlazeComponent.getComponentForElement(ui.item[0]);
                 if (boardComponent && boardComponent.setIsDragging) {
    -              if (boardComponent) boardComponent.setIsDragging(false);
    +              boardComponent.setIsDragging(false);
                 }
               } catch (e) {
                 // Silent fail
               }
    -
    +          
               // Re-enable dragscroll after list dragging is complete
               try {
                 dragscroll.reset();
               } catch (e) {
                 // Silent fail
               }
    -
    +          
               // Re-enable dragscroll on all swimlanes
               $('.js-swimlane').each(function() {
                 $(this).addClass('dragscroll');
               });
             }
           });
    -      // Reactively adjust handle when setting changes
    -      Tracker.autorun(() => {
    -        const newHandle = computeHandle();
    -        if ($listsGroup.data('uiSortable') || $listsGroup.data('sortable')) {
    -          try { $listsGroup.sortable('option', 'handle', newHandle); } catch (e) {}
    +    }
    +  });
    +  
    +  // Initialize sortable on ALL listsGroup elements (even empty ones)
    +  $listsGroupElements.each(function(index) {
    +    const $listsGroup = $(this);
    +    const $lists = $listsGroup.find('.js-list');
    +    
    +    // Only initialize on listsGroup elements that have the .js-lists class
    +    if ($listsGroup.hasClass('js-lists')) {
    +      $listsGroup.sortable({
    +        connectWith: '.js-swimlane, .js-lists',
    +        tolerance: 'pointer',
    +        appendTo: '.board-canvas',
    +        helper: 'clone',
    +        items: '.js-list:not(.js-list-composer)',
    +        placeholder: 'list placeholder',
    +        distance: 7,
    +        handle: '.js-list-handle',
    +        disabled: !Utils.canModifyBoard(),
    +        start(evt, ui) {
    +          ui.helper.css('z-index', 1000);
    +          ui.placeholder.height(ui.helper.height());
    +          ui.placeholder.width(ui.helper.width());
    +          EscapeActions.executeUpTo('popup-close');
    +          // Try to get board component
    +          try {
    +            const boardComponent = BlazeComponent.getComponentForElement(ui.item[0]);
    +            if (boardComponent && boardComponent.setIsDragging) {
    +              boardComponent.setIsDragging(true);
    +            }
    +          } catch (e) {
    +            // Silent fail
    +          }
    +        },
    +        stop(evt, ui) {
    +          // To attribute the new index number, we need to get the DOM element
    +          // of the previous and the following list -- if any.
    +          const prevListDom = ui.item.prev('.js-list').get(0);
    +          const nextListDom = ui.item.next('.js-list').get(0);
    +          const sortIndex = calculateIndex(prevListDom, nextListDom, 1);
    +
    +          const listDomElement = ui.item.get(0);
    +          if (!listDomElement) {
    +            return;
    +          }
    +          
    +          let list;
    +          try {
    +            list = Blaze.getData(listDomElement);
    +          } catch (error) {
    +            return;
    +          }
    +          
    +          if (!list) {
    +            return;
    +          }
    +
    +          // Detect if the list was dropped in a different swimlane
    +          const targetSwimlaneDom = ui.item.closest('.js-swimlane');
    +          let targetSwimlaneId = null;
    +
    +          if (targetSwimlaneDom.length > 0) {
    +            // List was dropped in a swimlane
    +            try {
    +              targetSwimlaneId = targetSwimlaneDom.attr('id').replace('swimlane-', '');
    +            } catch (error) {
    +              return;
    +            }
    +          } else {
    +            // List was dropped in lists view (not swimlanes view)
    +            // In this case, assign to the default swimlane
    +            const currentBoard = ReactiveCache.getBoard(Session.get('currentBoard'));
    +            if (currentBoard) {
    +              const defaultSwimlane = currentBoard.getDefaultSwimline();
    +              if (defaultSwimlane) {
    +                targetSwimlaneId = defaultSwimlane._id;
    +              }
    +            }
    +          }
    +
    +          // Get the original swimlane ID of the list (handle backward compatibility)
    +          const originalSwimlaneId = list.getEffectiveSwimlaneId ? list.getEffectiveSwimlaneId() : (list.swimlaneId || null);
    +
    +          // Prepare update object
    +          const updateData = {
    +            sort: sortIndex.base,
    +          };
    +
    +          // Check if the list was dropped in a different swimlane
    +          const isDifferentSwimlane = targetSwimlaneId && targetSwimlaneId !== originalSwimlaneId;
    +
    +          // If the list was dropped in a different swimlane, update the swimlaneId
    +          if (isDifferentSwimlane) {
    +            updateData.swimlaneId = targetSwimlaneId;
    +
    +            // Move all cards in the list to the new swimlane
    +            const cardsInList = ReactiveCache.getCards({
    +              listId: list._id,
    +              archived: false
    +            });
    +
    +            cardsInList.forEach(card => {
    +              card.move(list.boardId, targetSwimlaneId, list._id);
    +            });
    +
    +            // Don't cancel the sortable when moving to a different swimlane
    +            // The DOM move should be allowed to complete
    +          }
    +          // Allow reordering within the same swimlane by not canceling the sortable
    +
    +          try {
    +            Lists.update(list._id, {
    +              $set: updateData,
    +            });
    +          } catch (error) {
    +            return;
    +          }
    +
    +          // Try to get board component
    +          try {
    +            const boardComponent = BlazeComponent.getComponentForElement(ui.item[0]);
    +            if (boardComponent && boardComponent.setIsDragging) {
    +              boardComponent.setIsDragging(false);
    +            }
    +          } catch (e) {
    +            // Silent fail
    +          }
    +          
    +          // Re-enable dragscroll after list dragging is complete
    +          try {
    +            dragscroll.reset();
    +          } catch (e) {
    +            // Silent fail
    +          }
    +          
    +          // Re-enable dragscroll on all swimlanes
    +          $('.js-swimlane').each(function() {
    +            $(this).addClass('dragscroll');
    +          });
             }
           });
         }
    @@ -936,12 +979,13 @@ setTimeout(() => {
     
     
     
    -Template.listsGroup.helpers({
    +BlazeComponent.extendComponent({
       currentCardIsInThisList(listId, swimlaneId) {
         return currentCardIsInThisList(listId, swimlaneId);
       },
       visible(list) {
         if (list.archived) {
    +      // Show archived list only when filter archive is on
           if (!Filter.archive.isSelected()) {
             return false;
           }
    @@ -952,6 +996,8 @@ Template.listsGroup.helpers({
           }
         }
         if (Filter.hideEmpty.isSelected()) {
    +      // Check for cards in all swimlanes, not just the current one
    +      // This ensures lists with cards in other swimlanes are still visible
           const cards = list.cards();
           if (cards.length === 0) {
             return false;
    @@ -959,141 +1005,110 @@ Template.listsGroup.helpers({
         }
         return true;
       },
    -});
    +  onRendered() {
    +    const boardComponent = this.parentComponent();
    +    const $listsDom = this.$('.js-lists');
    +    
     
    -Template.listsGroup.onRendered(function () {
    -  const tpl = this;
    -  const boardBodyEl2 = tpl.firstNode?.parentElement?.closest?.('.board-body') || document.querySelector('.board-body');
    -  const boardView2 = boardBodyEl2 && Blaze.getView(boardBodyEl2, 'Template.boardBody');
    -  const boardComponent = boardView2?.templateInstance?.();
    -  const $listsDom = tpl.$('.js-lists');
    -
    -  if (!Utils.getCurrentCardId() && boardComponent) {
    -    boardComponent.scrollLeft();
    -  }
    -
    -  // Wait for DOM to be ready
    -  setTimeout(() => {
    -    const handleSelector = Utils.isTouchScreenOrShowDesktopDragHandles()
    -      ? '.js-list-handle'
    -      : '.js-list-header';
    -    const $lists = tpl.$('.js-list');
    -
    -    const $parent = $lists.parent();
    -
    -    // Initialize sortable even if there are no lists (to allow dropping into empty swimlanes)
    -    if ($parent.hasClass('js-lists')) {
    -
    -      // Check for drag handles
    -      const $handles = $parent.find('.js-list-handle');
    -
    -      // Test if drag handles are clickable
    -      $handles.on('click', function (e) {
    -        e.preventDefault();
    -        e.stopPropagation();
    -      });
    -
    -      $parent.sortable({
    -        connectWith: '.js-swimlane, .js-lists',
    -        tolerance: 'pointer',
    -        appendTo: '.board-canvas',
    -        helper: 'clone',
    -        items: '.js-list:not(.js-list-composer)',
    -        placeholder: 'list placeholder',
    -        distance: 7,
    -        handle: handleSelector,
    -        disabled: !Utils.canModifyBoard(),
    -        dropOnEmpty: true,
    -        start(evt, ui) {
    -          ui.helper.css('z-index', 1000);
    -          ui.placeholder.height(ui.helper.height());
    -          ui.placeholder.width(ui.helper.width());
    -          EscapeActions.executeUpTo('popup-close');
    -          if (boardComponent) boardComponent.setIsDragging(true);
    -        },
    -        stop(evt, ui) {
    -          if (boardComponent) boardComponent.setIsDragging(false);
    -        },
    -      });
    -      // Reactively update handle when user toggles desktop drag handles
    -      tpl.autorun(() => {
    -        const newHandle = Utils.isTouchScreenOrShowDesktopDragHandles()
    -          ? '.js-list-handle'
    -          : '.js-list-header';
    -        if ($parent.data('uiSortable') || $parent.data('sortable')) {
    -          try {
    -            $parent.sortable('option', 'handle', newHandle);
    -          } catch (e) {}
    -        }
    -      });
    +    if (!Utils.getCurrentCardId()) {
    +      boardComponent.scrollLeft();
         }
    -  }, 100);
    -});
    +
    +    // Try a simpler approach for listsGroup too
    +    
    +    // Wait for DOM to be ready
    +    setTimeout(() => {
    +      const $lists = this.$('.js-list');
    +      
    +      const $parent = $lists.parent();
    +      
    +      if ($lists.length > 0) {
    +        
    +        // Check for drag handles
    +        const $handles = $parent.find('.js-list-handle');
    +        
    +        // Test if drag handles are clickable
    +        $handles.on('click', function(e) {
    +          e.preventDefault();
    +          e.stopPropagation();
    +        });
    +        
    +        $parent.sortable({
    +          connectWith: '.js-swimlane, .js-lists',
    +          tolerance: 'pointer',
    +          appendTo: '.board-canvas',
    +          helper: 'clone',
    +          items: '.js-list:not(.js-list-composer)',
    +          placeholder: 'list placeholder',
    +          distance: 7,
    +          handle: '.js-list-handle',
    +          disabled: !Utils.canModifyBoard(),
    +          start(evt, ui) {
    +            ui.helper.css('z-index', 1000);
    +            ui.placeholder.height(ui.helper.height());
    +            ui.placeholder.width(ui.helper.width());
    +            EscapeActions.executeUpTo('popup-close');
    +            boardComponent.setIsDragging(true);
    +          },
    +          stop(evt, ui) {
    +            boardComponent.setIsDragging(false);
    +          }
    +        });
    +      } else {
    +      }
    +    }, 100);
    +  },
    +}).register('listsGroup');
     
     
    -function swimlaneBoardsSelector(excludeCurrentBoard) {
    -  const selector = {
    -    archived: false,
    -    'members.userId': Meteor.userId(),
    -    type: 'board',
    -  };
    -  if (excludeCurrentBoard) {
    -    selector._id = { $ne: Utils.getCurrentBoard()._id };
    +class MoveSwimlaneComponent extends BlazeComponent {
    +  serverMethod = 'moveSwimlane';
    +
    +  onCreated() {
    +    this.currentSwimlane = this.currentData();
       }
    -  return selector;
    -}
     
    -function swimlaneToBoards(excludeCurrentBoard) {
    -  return ReactiveCache.getBoards(
    -    swimlaneBoardsSelector(excludeCurrentBoard),
    -    { sort: { title: 1 } },
    -  );
    -}
    -
    -function swimlaneDoneEvent(serverMethod, tpl) {
    -  const bSelect = tpl.$('.js-select-boards')[0];
    -  let boardId;
    -  if (bSelect) {
    -    boardId = bSelect.options[bSelect.selectedIndex].value;
    -    Meteor.call(serverMethod, tpl.currentSwimlane._id, boardId);
    -  }
    -  Popup.back();
    -}
    -
    -Template.moveSwimlanePopup.onCreated(function () {
    -  this.currentSwimlane = Template.currentData();
    -});
    -
    -Template.moveSwimlanePopup.helpers({
       board() {
         return Utils.getCurrentBoard();
    -  },
    +  }
    +
    +  toBoardsSelector() {
    +    return {
    +      archived: false,
    +      'members.userId': Meteor.userId(),
    +      type: 'board',
    +      _id: { $ne: this.board()._id },
    +    };
    +  }
    +
       toBoards() {
    -    return swimlaneToBoards(true);
    -  },
    -});
    +    const ret = ReactiveCache.getBoards(this.toBoardsSelector(), { sort: { title: 1 } });
    +    return ret;
    +  }
     
    -Template.moveSwimlanePopup.events({
    -  'click .js-done'(event, tpl) {
    -    swimlaneDoneEvent('moveSwimlane', tpl);
    -  },
    -});
    +  events() {
    +    return [
    +      {
    +        'click .js-done'() {
    +          const bSelect = $('.js-select-boards')[0];
    +          let boardId;
    +          if (bSelect) {
    +            boardId = bSelect.options[bSelect.selectedIndex].value;
    +            Meteor.call(this.serverMethod, this.currentSwimlane._id, boardId);
    +          }
    +          Popup.back();
    +        },
    +      },
    +    ];
    +  }
    +}
    +MoveSwimlaneComponent.register('moveSwimlanePopup');
     
    -Template.copySwimlanePopup.onCreated(function () {
    -  this.currentSwimlane = Template.currentData();
    -});
    -
    -Template.copySwimlanePopup.helpers({
    -  board() {
    -    return Utils.getCurrentBoard();
    -  },
    -  toBoards() {
    -    return swimlaneToBoards(false);
    -  },
    -});
    -
    -Template.copySwimlanePopup.events({
    -  'click .js-done'(event, tpl) {
    -    swimlaneDoneEvent('copySwimlane', tpl);
    -  },
    -});
    +(class extends MoveSwimlaneComponent {
    +  serverMethod = 'copySwimlane';
    +  toBoardsSelector() {
    +    const selector = super.toBoardsSelector();
    +    delete selector._id;
    +    return selector;
    +  }
    +}.register('copySwimlanePopup'));
    diff --git a/client/components/unicode-icons.css b/client/components/unicode-icons.css
    deleted file mode 100644
    index 9afc50ccd..000000000
    --- a/client/components/unicode-icons.css
    +++ /dev/null
    @@ -1,33 +0,0 @@
    -.unicode-icon {
    -  filter: grayscale(100%);
    -  -webkit-filter: grayscale(100%);
    -  opacity: 0.8;
    -  display: inline-block;
    -  line-height: 1;
    -}
    -
    -/* Greyscale for explicitly-marked emoji when feature is enabled */
    -body.grey-icons-enabled .emoji-icon {
    -  filter: grayscale(100%);
    -  -webkit-filter: grayscale(100%);
    -  opacity: 0.85;
    -  display: inline-block;
    -}
    -
    -/* When grey icons are enabled, also grey common UI badges and toggles */
    -body.grey-icons-enabled .card-date,
    -body.grey-icons-enabled .mobile-icon,
    -body.grey-icons-enabled .desktop-icon {
    -  filter: grayscale(100%);
    -  -webkit-filter: grayscale(100%);
    -  opacity: 0.85;
    -}
    -
    -/* Grey card minibadges (icons + text + backgrounds) */
    -body.grey-icons-enabled .minicard .badges .badge,
    -body.grey-icons-enabled .minicard .badges .badge .badge-icon,
    -body.grey-icons-enabled .minicard .badges .badge .badge-text {
    -  filter: grayscale(100%);
    -  -webkit-filter: grayscale(100%);
    -  opacity: 0.9;
    -}
    diff --git a/client/components/unicode-icons.js b/client/components/unicode-icons.js
    deleted file mode 100644
    index 709d896cd..000000000
    --- a/client/components/unicode-icons.js
    +++ /dev/null
    @@ -1,122 +0,0 @@
    -Meteor.startup(() => {
    -  const greyscaleIcons = [
    -    '🔼', '❌', '🏷️', '📅', '📥', '🚀', '👤', '👥', '✍️', '📋', '✏️', '🌐', '📎', '📝', '📋', '📜', '🏠', '🔒', '🔕', '🃏',
    -    '⏰', '🛒', '🔢', '✅', '❌', '👁️', '👍', '👎', '📋', '🕐', '🎨',
    -    '📤', '⬆️', '⬇️', '➡️', '📦',
    -    '⬅️', '↕️', '🔽', '🔍', '▼', '🏊',
    -    '🔔', '⚙️', '🖼️', '🔑', '🚪', '◀️', '⌨️', '👥', '🏷️', '✅', '🚫', '☑️', '💬',
    -    // Mobile/Desktop toggle + calendar
    -    '📱', '🖥️', '🗓️'
    -  ];
    -
    -  const EXCLUDE_SELECTOR = '.header-user-bar-avatar, .avatar-initials, script, style';
    -  let observer = null;
    -  let enabled = false;
    -
    -  function isExcluded(el) {
    -    if (!el) return true;
    -    if (el.nodeType === Node.ELEMENT_NODE && (el.matches('script') || el.matches('style'))) return true;
    -    if (el.closest && el.closest(EXCLUDE_SELECTOR)) return true;
    -    return false;
    -  }
    -
    -  function wrapTextNodeOnce(parent, textNode) {
    -    if (!parent || !textNode) return;
    -    if (isExcluded(parent)) return;
    -    if (parent.closest && parent.closest('.unicode-icon')) return;
    -    const raw = textNode.nodeValue;
    -    if (!raw) return;
    -    const txt = raw.trim();
    -    // small guard against long text processing
    -    if (txt.length > 3) return;
    -    if (!greyscaleIcons.includes(txt)) return;
    -    const span = document.createElement('span');
    -    span.className = 'unicode-icon';
    -    span.textContent = txt;
    -    parent.replaceChild(span, textNode);
    -  }
    -
    -  function wrapSubtree(root) {
    -    try {
    -      if (!root) return;
    -      // Walk only within this subtree for text nodes
    -      const walker = document.createTreeWalker(
    -        root.nodeType === Node.ELEMENT_NODE ? root : root.parentNode || document.body,
    -        NodeFilter.SHOW_TEXT,
    -        {
    -          acceptNode: (node) => {
    -            if (!node || !node.nodeValue) return NodeFilter.FILTER_REJECT;
    -            const parent = node.parentNode;
    -            if (!parent || isExcluded(parent)) return NodeFilter.FILTER_REJECT;
    -            if (parent.closest && parent.closest('.unicode-icon')) return NodeFilter.FILTER_REJECT;
    -            const txt = node.nodeValue.trim();
    -            if (!txt || txt.length > 3) return NodeFilter.FILTER_REJECT;
    -            return greyscaleIcons.includes(txt) ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_SKIP;
    -          },
    -        },
    -        false,
    -      );
    -      const toWrap = [];
    -      while (walker.nextNode()) {
    -        toWrap.push(walker.currentNode);
    -      }
    -      for (const textNode of toWrap) {
    -        wrapTextNodeOnce(textNode.parentNode, textNode);
    -      }
    -    } catch (_) {}
    -  }
    -
    -  function processInitial() {
    -    // Process only frequently used UI containers to avoid full-page walks
    -    const roots = [document.body].filter(Boolean);
    -    roots.forEach(wrapSubtree);
    -  }
    -
    -  function startObserver() {
    -    if (observer) return;
    -    observer = new MutationObserver((mutations) => {
    -      // Batch process only added nodes, ignore attribute/character changes
    -      for (const m of mutations) {
    -        if (m.type !== 'childList') continue;
    -        m.addedNodes && m.addedNodes.forEach((n) => {
    -          // Process only within the newly added subtree
    -          wrapSubtree(n);
    -        });
    -      }
    -    });
    -    observer.observe(document.body, { childList: true, subtree: true });
    -  }
    -
    -  function stopObserver() {
    -    if (observer) {
    -      try { observer.disconnect(); } catch (_) {}
    -    }
    -    observer = null;
    -  }
    -
    -  function enableGrey() {
    -    if (enabled) return;
    -    enabled = true;
    -    try { document.body.classList.add('grey-icons-enabled'); } catch (_) {}
    -    Meteor.defer(processInitial);
    -    startObserver();
    -  }
    -
    -  function disableGrey() {
    -    if (!enabled) return;
    -    enabled = false;
    -    stopObserver();
    -    try { document.body.classList.remove('grey-icons-enabled'); } catch (_) {}
    -    // unwrap existing
    -    document.querySelectorAll('span.unicode-icon').forEach((span) => {
    -      const txt = document.createTextNode(span.textContent || '');
    -      if (span.parentNode) span.parentNode.replaceChild(txt, span);
    -    });
    -  }
    -
    -  Tracker.autorun(() => {
    -    const user = Meteor.user();
    -    const on = !!(user && user.profile && user.profile.GreyIcons);
    -    if (on) enableGrey(); else disableGrey();
    -  });
    -});
    diff --git a/client/components/users/passwordInput.jade b/client/components/users/passwordInput.jade
    deleted file mode 100644
    index 9cc750c73..000000000
    --- a/client/components/users/passwordInput.jade
    +++ /dev/null
    @@ -1,12 +0,0 @@
    -template(name='passwordInput')
    -  .at-input
    -    label(for='at-field-{{_id}}') {{displayName}}
    -    .password-input-container
    -      input.password-field(type="{{type}}" placeholder="{{displayName}}" autocomplete="{{autocomplete}}" required="{{required}}")
    -      button.password-toggle-btn(type="button" tabindex="-1" aria-label="Toggle password visibility" title="Toggle password visibility")
    -        i.fa.fa-eye.eye-icon
    -        i.fa.fa-eye-slash.eye-slash-icon
    -    if errs
    -      .at-error
    -        each errs
    -          div {{this}}
    \ No newline at end of file
    diff --git a/client/components/users/passwordInput.js b/client/components/users/passwordInput.js
    deleted file mode 100644
    index 325cef8d1..000000000
    --- a/client/components/users/passwordInput.js
    +++ /dev/null
    @@ -1,57 +0,0 @@
    -import { TAPi18n } from '/imports/i18n';
    -
    -Template.passwordInput.onRendered(function() {
    -  const template = this;
    -  const input = template.find('input.password-field');
    -  const label = template.find('label');
    -
    -  // Set the dynamic id and name based on the field _id
    -  if (template.data && template.data._id) {
    -    const fieldId = `at-field-${template.data._id}`;
    -    input.id = fieldId;
    -    input.name = fieldId;
    -    label.setAttribute('for', fieldId);
    -
    -    // Ensure the input starts as password type for password fields
    -    input.type = 'password';
    -
    -    // Initially show eye icon (password is hidden) and hide eye-slash icon
    -    const eyeIcon = template.find('.eye-icon');
    -    const eyeSlashIcon = template.find('.eye-slash-icon');
    -    if (eyeIcon) {
    -      eyeIcon.style.display = 'inline-block';
    -    }
    -    if (eyeSlashIcon) {
    -      eyeSlashIcon.style.display = 'none';
    -    }
    -  }
    -});
    -
    -Template.passwordInput.events({
    -  'click .password-toggle-btn'(event, template) {
    -    event.preventDefault();
    -    const input = template.find('input.password-field');
    -    const eyeIcon = template.find('.eye-icon');
    -    const eyeSlashIcon = template.find('.eye-slash-icon');
    -
    -    if (input.type === 'password') {
    -      input.type = 'text';
    -      // Show eye-slash icon when password is visible
    -      if (eyeIcon) {
    -        eyeIcon.style.display = 'none';
    -      }
    -      if (eyeSlashIcon) {
    -        eyeSlashIcon.style.display = 'inline-block';
    -      }
    -    } else {
    -      input.type = 'password';
    -      // Show eye icon when password is hidden
    -      if (eyeIcon) {
    -        eyeIcon.style.display = 'inline-block';
    -      }
    -      if (eyeSlashIcon) {
    -        eyeSlashIcon.style.display = 'none';
    -      }
    -    }
    -  },
    -});
    \ No newline at end of file
    diff --git a/client/components/users/userAvatar.css b/client/components/users/userAvatar.css
    index 27d8993b7..b65a98bd9 100644
    --- a/client/components/users/userAvatar.css
    +++ b/client/components/users/userAvatar.css
    @@ -3,8 +3,8 @@
       display: block;
       position: relative;
       float: left;
    -  height: clamp(24px, 3.5vw, 36px);
    -  width: clamp(24px, 3.5vw, 36px);
    +  height: 30px;
    +  width: 30px;
       margin: .3vh;
       cursor: pointer;
       user-select: none;
    @@ -23,9 +23,6 @@
       background-color: #dbdbdb;
       color: #444;
       position: absolute;
    -  display: flex;
    -  align-items: center;
    -  justify-content: center;
     }
     .member .avatar.avatar-image {
       object-fit: cover;
    @@ -114,7 +111,7 @@
       padding-top: 0;
     }
     .mini-profile-info .member {
    -  width: clamp(40px, 5vw, 60px);
    -  height: clamp(40px, 5vw, 60px);
    +  width: 50px;
    +  height: 50px;
       margin-right: 10px;
     }
    diff --git a/client/components/users/userAvatar.jade b/client/components/users/userAvatar.jade
    index 1905e4c79..e00fc188f 100644
    --- a/client/components/users/userAvatar.jade
    +++ b/client/components/users/userAvatar.jade
    @@ -1,12 +1,9 @@
     template(name="userAvatar")
    -  a.member(class="js-{{#if assignee}}assignee{{else}}member{{/if}}" title="{{#if userData}}{{userData.profile.fullname}} ({{userData.username}}) {{_ memberType}}{{/if}}")
    -    if userData
    -      if userData.profile.avatarUrl
    -        img.avatar.avatar-image(src="{{avatarUrl}}")
    -      else
    -        +userAvatarInitials(userId=userData._id)
    +  a.member(class="js-{{#if assignee}}assignee{{else}}member{{/if}}" title="{{userData.profile.fullname}} ({{userData.username}}) {{_ memberType}}")
    +    if userData.profile.avatarUrl
    +      img.avatar.avatar-image(src="{{avatarUrl}}")
         else
    -      +userAvatarInitials(userId=this.userId)
    +      +userAvatarInitials(userId=userData._id)
     
         if showStatus
           span.member-presence-status(class=presenceStatusClassName)
    @@ -16,11 +13,11 @@ template(name="userAvatar")
           if showEdit
             if $eq currentUser._id userData._id
               a.edit-avatar.js-change-avatar
    -            i.fa.fa-pencil-square-o
    +            | ✏️
     
     template(name="userAvatarInitials")
       svg.avatar.avatar-initials(viewBox="0 0 {{viewPortWidth}} 15")
    -    text(x="50%" y="11" text-anchor="middle" dominant-baseline="middle" font-size="16")= initials
    +    text(x="50%" y="13" text-anchor="middle")= initials
     
     template(name="orgAvatar")
       a.member.orgOrTeamMember(class="js-member" title="{{orgData.orgDisplayName}}")
    @@ -56,7 +53,7 @@ template(name="boardTeamRow")
     
     template(name="boardOrgName")
       svg.avatar.avatar-initials(viewBox="0 0 {{orgViewPortWidth}} 15")
    -    text(x="50%" y="11" text-anchor="middle" dominant-baseline="middle" font-size="16")= orgName
    +    text(x="50%" y="13" text-anchor="middle")= orgName
     
     template(name="teamAvatar")
       a.member.orgOrTeamMember(class="js-member" title="{{teamData.teamDisplayName}}")
    @@ -64,7 +61,7 @@ template(name="teamAvatar")
     
     template(name="boardTeamName")
       svg.avatar.avatar-initials(viewBox="0 0 {{teamViewPortWidth}} 15")
    -    text(x="50%" y="11" text-anchor="middle" dominant-baseline="middle" font-size="16")= teamName
    +    text(x="50%" y="13" text-anchor="middle")= teamName
     
     template(name="userPopup")
       .board-member-menu
    @@ -91,19 +88,21 @@ template(name="changeAvatarPopup")
           li: a.js-select-avatar
             .member
               img.avatar.avatar-image(src="{{link}}")
    +        | {{_ 'uploaded-avatar'}}
             if isSelected
    -          i.fa.fa-check
    -          p.sub-name
    -          a.js-delete-avatar {{_ 'delete'}}
    -          |  -
    +          | ✅
    +        p.sub-name
    +          unless isSelected
    +            a.js-delete-avatar {{_ 'delete'}}
    +            |  -
               = name
         li: a.js-select-initials
           .member
             +userAvatarInitials(userId=currentUser._id)
           | {{_ 'initials' }}
           if noAvatarUrl
    -        i.fa.fa-check
    -        p.sub-name {{_ 'default-avatar'}}
    +        | ✅
    +       p.sub-name {{_ 'default-avatar'}}
       input.hide.js-upload-avatar-input(accept="image/*;capture=camera" type="file")
       if Meteor.settings.public.avatarsUploadMaxSize
         | {{_ 'max-avatar-filesize'}} {{Meteor.settings.public.avatarsUploadMaxSize}}
    @@ -113,12 +112,8 @@ template(name="changeAvatarPopup")
         br
       | {{_ 'invalid-file'}}
       button.full.js-upload-avatar
    -    i.fa.fa-upload
    -    | {{_ 'upload-avatar' }}
    -
    -template(name="deleteAvatarPopup")
    -  p {{_ 'delete-avatar-confirm'}}
    -  button.js-confirm.negate.full(type="submit") {{_ 'delete'}}
    +    | 📤
    +    | {{_ 'upload-avatar'}}
     
     template(name="cardMemberPopup")
       .board-member-menu
    diff --git a/client/components/users/userAvatar.js b/client/components/users/userAvatar.js
    index 4b56ecd02..f2db90ee3 100644
    --- a/client/components/users/userAvatar.js
    +++ b/client/components/users/userAvatar.js
    @@ -7,13 +7,12 @@ import Team from '/models/team';
     
     Template.userAvatar.helpers({
       userData() {
    -    const user = ReactiveCache.getUser(this.userId, {
    +    return ReactiveCache.getUser(this.userId, {
           fields: {
             profile: 1,
             username: 1,
           },
         });
    -    return user;
       },
     
       avatarUrl() {
    @@ -33,21 +32,7 @@ Template.userAvatar.helpers({
     
       memberType() {
         const user = ReactiveCache.getUser(this.userId);
    -    if (!user) return '';
    -
    -    const board = Utils.getCurrentBoard();
    -    if (!board) return '';
    -
    -    // Return role in priority order: Admin, Normal, NormalAssignedOnly, NoComments, CommentOnly, CommentAssignedOnly, Worker, ReadOnly, ReadAssignedOnly
    -    if (user.isBoardAdmin()) return 'admin';
    -    if (board.hasReadAssignedOnly(user._id)) return 'read-assigned-only';
    -    if (board.hasReadOnly(user._id)) return 'read-only';
    -    if (board.hasWorker(user._id)) return 'worker';
    -    if (board.hasCommentAssignedOnly(user._id)) return 'comment-assigned-only';
    -    if (board.hasCommentOnly(user._id)) return 'comment-only';
    -    if (board.hasNoComments(user._id)) return 'no-comments';
    -    if (board.hasNormalAssignedOnly(user._id)) return 'normal-assigned-only';
    -    return 'normal';
    +    return user && user.isBoardAdmin() ? 'admin' : 'normal';
       },
     
     /*
    @@ -76,38 +61,53 @@ Template.userAvatarInitials.helpers({
       },
     });
     
    -Template.boardOrgRow.onCreated(function () {
    -  this.error = new ReactiveVar('');
    -  this.loading = new ReactiveVar(false);
    -  this.findOrgsOptions = new ReactiveVar({});
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.error = new ReactiveVar('');
    +    this.loading = new ReactiveVar(false);
    +    this.findOrgsOptions = new ReactiveVar({});
     
    -  this.page = new ReactiveVar(1);
    -  this.autorun(() => {
    -    const limitOrgs = this.page.get() * Number.MAX_SAFE_INTEGER;
    -    this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => {});
    -  });
    -});
    +    this.page = new ReactiveVar(1);
    +    this.autorun(() => {
    +      const limitOrgs = this.page.get() * Number.MAX_SAFE_INTEGER;
    +      this.subscribe('org', this.findOrgsOptions.get(), limitOrgs, () => {});
    +    });
    +  },
     
    -Template.boardOrgRow.onRendered(function () {
    -  this.loading.set(false);
    -});
    +  onRendered() {
    +    this.setLoading(false);
    +  },
    +
    +  setError(error) {
    +    this.error.set(error);
    +  },
    +
    +  setLoading(w) {
    +    this.loading.set(w);
    +  },
    +
    +  isLoading() {
    +    return this.loading.get();
    +  },
    +
    +  events() {
    +    return [
    +      {
    +        'keyup input'() {
    +          this.setError('');
    +        },
    +        'click .js-manage-board-removeOrg': Popup.open('removeBoardOrg'),
    +      },
    +    ];
    +  },
    +}).register('boardOrgRow');
     
     Template.boardOrgRow.helpers({
    -  isLoading() {
    -    return Template.instance().loading.get();
    -  },
       orgData() {
         return ReactiveCache.getOrg(this.orgId);
       },
     });
     
    -Template.boardOrgRow.events({
    -  'keyup input'(event, tpl) {
    -    tpl.error.set('');
    -  },
    -  'click .js-manage-board-removeOrg': Popup.open('removeBoardOrg'),
    -});
    -
     Template.boardOrgName.helpers({
       orgName() {
         const org = ReactiveCache.getOrg(this.orgId);
    @@ -120,38 +120,53 @@ Template.boardOrgName.helpers({
       },
     });
     
    -Template.boardTeamRow.onCreated(function () {
    -  this.error = new ReactiveVar('');
    -  this.loading = new ReactiveVar(false);
    -  this.findOrgsOptions = new ReactiveVar({});
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.error = new ReactiveVar('');
    +    this.loading = new ReactiveVar(false);
    +    this.findOrgsOptions = new ReactiveVar({});
     
    -  this.page = new ReactiveVar(1);
    -  this.autorun(() => {
    -    const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER;
    -    this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {});
    -  });
    -});
    +    this.page = new ReactiveVar(1);
    +    this.autorun(() => {
    +      const limitTeams = this.page.get() * Number.MAX_SAFE_INTEGER;
    +      this.subscribe('team', this.findOrgsOptions.get(), limitTeams, () => {});
    +    });
    +  },
     
    -Template.boardTeamRow.onRendered(function () {
    -  this.loading.set(false);
    -});
    +  onRendered() {
    +    this.setLoading(false);
    +  },
    +
    +  setError(error) {
    +    this.error.set(error);
    +  },
    +
    +  setLoading(w) {
    +    this.loading.set(w);
    +  },
    +
    +  isLoading() {
    +    return this.loading.get();
    +  },
    +
    +  events() {
    +    return [
    +      {
    +        'keyup input'() {
    +          this.setError('');
    +        },
    +        'click .js-manage-board-removeTeam': Popup.open('removeBoardTeam'),
    +      },
    +    ];
    +  },
    +}).register('boardTeamRow');
     
     Template.boardTeamRow.helpers({
    -  isLoading() {
    -    return Template.instance().loading.get();
    -  },
       teamData() {
         return ReactiveCache.getTeam(this.teamId);
       },
     });
     
    -Template.boardTeamRow.events({
    -  'keyup input'(event, tpl) {
    -    tpl.error.set('');
    -  },
    -  'click .js-manage-board-removeTeam': Popup.open('removeBoardTeam'),
    -});
    -
     Template.boardTeamName.helpers({
       teamName() {
         const team = ReactiveCache.getTeam(this.teamId);
    @@ -164,78 +179,97 @@ Template.boardTeamName.helpers({
       },
     });
     
    -Template.changeAvatarPopup.onCreated(function () {
    -  this.error = new ReactiveVar('');
    -  Meteor.subscribe('my-avatars');
    -});
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    this.error = new ReactiveVar('');
     
    -Template.changeAvatarPopup.helpers({
    -  error() {
    -    return Template.instance().error;
    +    Meteor.subscribe('my-avatars');
       },
    +
       uploadedAvatars() {
    -    const ret = ReactiveCache.getAvatars({ userId: Meteor.userId() }, {}, true);
    +    const ret = ReactiveCache.getAvatars({ userId: Meteor.userId() }, {}, true).each();
         return ret;
       },
    +
       isSelected() {
         const userProfile = ReactiveCache.getCurrentUser().profile;
         const avatarUrl = userProfile && userProfile.avatarUrl;
    -    const currentAvatarUrl = Template.currentData().link();
    +    const currentAvatarUrl = this.currentData().link();
         return avatarUrl === currentAvatarUrl;
       },
    +
       noAvatarUrl() {
         const userProfile = ReactiveCache.getCurrentUser().profile;
         const avatarUrl = userProfile && userProfile.avatarUrl;
         return !avatarUrl;
       },
    +
    +  setAvatar(avatarUrl) {
    +    ReactiveCache.getCurrentUser().setAvatarUrl(avatarUrl);
    +  },
    +
    +  setError(error) {
    +    this.error.set(error);
    +  },
    +
    +  events() {
    +    return [
    +      {
    +        'click .js-upload-avatar'() {
    +          this.$('.js-upload-avatar-input').click();
    +        },
    +        'change .js-upload-avatar-input'(event) {
    +          const self = this;
    +          if (event.currentTarget.files && event.currentTarget.files[0]) {
    +            const uploader = Avatars.insert(
    +              {
    +                file: event.currentTarget.files[0],
    +                chunkSize: 'dynamic',
    +              },
    +              false,
    +            );
    +            uploader.on('error', (error, fileData) => {
    +              self.setError(error.reason);
    +            });
    +            uploader.start();
    +          }
    +        },
    +        'click .js-select-avatar'() {
    +          const avatarUrl = this.currentData().link();
    +          this.setAvatar(avatarUrl);
    +        },
    +        'click .js-select-initials'() {
    +          this.setAvatar('');
    +        },
    +        'click .js-delete-avatar'(event) {
    +          Avatars.remove(this.currentData()._id);
    +          event.stopPropagation();
    +        },
    +      },
    +    ];
    +  },
    +}).register('changeAvatarPopup');
    +
    +Template.cardMembersPopup.helpers({
    +  isCardMember() {
    +    const card = Template.parentData();
    +    const cardMembers = card.getMembers();
    +
    +    return _.contains(cardMembers, this.userId);
    +  },
    +
    +  user() {
    +    return ReactiveCache.getUser(this.userId);
    +  },
     });
     
    -function changeAvatarSetAvatar(tpl, avatarUrl) {
    -  Meteor.call('setAvatarUrl', avatarUrl, (err) => {
    -    if (err) {
    -      tpl.error.set(err.reason || 'Error setting avatar');
    -    }
    -  });
    -}
    -
    -Template.changeAvatarPopup.events({
    -  'click .js-upload-avatar'(event, tpl) {
    -    tpl.$('.js-upload-avatar-input').click();
    -  },
    -  'change .js-upload-avatar-input'(event, tpl) {
    -    if (event.currentTarget.files && event.currentTarget.files[0]) {
    -      const uploader = Avatars.insert(
    -        {
    -          file: event.currentTarget.files[0],
    -          chunkSize: 'dynamic',
    -        },
    -        false,
    -      );
    -      uploader.on('error', (error, fileData) => {
    -        tpl.error.set(error.reason);
    -      });
    -      uploader.start();
    -    }
    -  },
    -  'click .js-select-avatar'(event, tpl) {
    +Template.cardMembersPopup.events({
    +  'click .js-select-member'(event) {
    +    const card = Utils.getCurrentCard();
    +    const memberId = this.userId;
    +    card.toggleMember(memberId);
         event.preventDefault();
    -    event.stopPropagation();
    -    const data = Blaze.getData(event.currentTarget);
    -    if (data && typeof data.link === 'function') {
    -      const avatarUrl = data.link();
    -      changeAvatarSetAvatar(tpl, avatarUrl);
    -    }
       },
    -  'click .js-select-initials'(event, tpl) {
    -    event.preventDefault();
    -    event.stopPropagation();
    -    changeAvatarSetAvatar(tpl, '');
    -  },
    -  'click .js-delete-avatar': Popup.afterConfirm('deleteAvatar', function (event) {
    -    Avatars.remove(this._id);
    -    Popup.back();
    -    event.stopPropagation();
    -  }),
     });
     
     Template.cardMemberPopup.helpers({
    diff --git a/client/components/users/userForm.css b/client/components/users/userForm.css
    index be5e0522d..89c572a4b 100644
    --- a/client/components/users/userForm.css
    +++ b/client/components/users/userForm.css
    @@ -24,67 +24,6 @@
     .auth-layout .auth-dialog .at-form input {
       width: 100%;
     }
    -.password-input-container {
    -  position: relative;
    -  display: flex;
    -  align-items: center;
    -}
    -.password-input-container input {
    -  flex: 1;
    -  padding-right: 55px; /* More room for the bigger button */
    -  box-sizing: border-box;
    -}
    -.password-toggle-btn {
    -  position: absolute;
    -  right: 5px; /* Adjusted for larger button */
    -  top: calc(50% - 26px); /* Moved up by 20px + 6px = 26px total */
    -  transform: translateY(-50%);
    -  background: #f8f8f8 !important;
    -  border: 1px solid #ddd !important;
    -  border-radius: 3px !important;
    -  color: #000 !important; /* Black color for the icon */
    -  cursor: pointer;
    -  padding: 8px 6px 8px 12px; /* 2x bigger padding, 6px less on right */
    -  font-size: 16px; /* 2x bigger font size */
    -  width: auto !important;
    -  height: auto !important;
    -  line-height: 1;
    -  display: flex !important;
    -  align-items: center;
    -  justify-content: center;
    -  z-index: 10;
    -  min-width: 40px; /* 2x bigger minimum width */
    -  min-height: 32px; /* 2x bigger minimum height */
    -}
    -/* Adjust position for login and register pages */
    -.auth-layout .password-toggle-btn {
    -  top: calc(50% - 11px); /* Move 15px down for login/register */
    -}
    -.password-toggle-btn .eye-text {
    -  color: #000 !important;
    -  font-size: 16px !important;
    -  line-height: 1;
    -  filter: grayscale(100%);
    -  -webkit-filter: grayscale(100%);
    -  opacity: 0.8;
    -}
    -.eye-slash-line {
    -  position: absolute;
    -  top: 10px;
    -  left: 10px;
    -  width: 20px;
    -  height: 20px;
    -  pointer-events: none;
    -  stroke: #000;
    -  stroke-width: 2;
    -  fill: none;
    -}
    -.password-toggle-btn:hover .eye-text {
    -  color: #000 !important;
    -  filter: grayscale(100%);
    -  -webkit-filter: grayscale(100%);
    -  opacity: 0.8;
    -}
     .auth-layout .auth-dialog .at-form button {
       width: 100%;
       background: #216694;
    diff --git a/client/components/users/userHeader.jade b/client/components/users/userHeader.jade
    index c095db48a..8934ddbc4 100644
    --- a/client/components/users/userHeader.jade
    +++ b/client/components/users/userHeader.jade
    @@ -12,98 +12,95 @@ template(name="headerUserBar")
     
     template(name="memberMenuPopup")
       ul.pop-over-list
    +    // Bookmarks at the very top
    +    li
    +      a.js-open-bookmarks
    +        | 🔖
    +        | {{_ 'bookmarks'}}
         with currentUser
    -      li
    -        a.js-toggle-grey-icons(href="#")
    -          i.fa.fa-paint-brush
    -          | {{_ 'grey-icons'}}
    -          if currentUser.profile
    -            if currentUser.profile.GreyIcons
    -              i.fa.fa-check
           li
             a.js-my-cards(href="{{pathFor 'my-cards'}}")
    -          i.fa.fa-list
    +          | 📋
               | {{_ 'my-cards'}}
           li
             a.js-due-cards(href="{{pathFor 'due-cards'}}")
    -          i.fa.fa-calendar
    +          | 📅
               | {{_ 'dueCards-title'}}
           li
             a.js-global-search(href="{{pathFor 'global-search'}}")
    -          i.fa.fa-search
    +          | 🔍
               | {{_ 'globalSearch-title'}}
           li
             a(href="{{pathFor 'home'}}")
    -          i.fa.fa-home
    +          | 🏠
               | {{_ 'all-boards'}}
           li
             a(href="{{pathFor 'public'}}")
    -          i.fa.fa-globe
    +          | 🌐
               | {{_ 'public'}}
           li
             a.board-header-btn.js-open-archived-board
    -          i.fa.fa-archive
    +          | 📦
               span {{_ 'archives'}}
           li
             a.js-notifications-drawer-toggle
    -          i.fa.fa-bell
    +          | 🔔
               | {{_ 'notifications'}}
           if currentSetting.customHelpLinkUrl
             li
               a(href="{{currentSetting.customHelpLinkUrl}}", title="{{_ 'help'}}", target="_blank", rel="noopener noreferrer")
    -            i.fa.fa-question-circle
    +            | ❓
                 | {{_ 'help'}}
           unless currentUser.isWorker
             ul.pop-over-list
               li
                 a(href="{{pathFor 'board' id=templatesBoardId slug=templatesBoardSlug}}")
    -              i.fa.fa-list
    +              | 📋
                   | {{_ 'templates'}}
           if currentUser.isAdmin
             li
               a.js-go-setting(href="{{pathFor 'setting'}}")
    -            i.fa.fa-lock
    +            | 🔒
                 | {{_ 'admin-panel'}}
           hr
           if isSameDomainNameSettingValue
             li
               a.js-invite-people
    -            i.fa.fa-envelope
    +            | ✉️
                 | {{_ 'invite-people'}}
           if isNotOAuth2AuthenticationMethod
             li
               a.js-edit-profile
    -            i.fa.fa-user
    +            | 👤
                 | {{_ 'edit-profile'}}
           li
             a.js-change-settings
    -          i.fa.fa-cog
    +          | ⚙️
               | {{_ 'change-settings'}}
           li
             a.js-change-avatar
    -          i.fa.fa-picture-o
    +          | 🖼️
               | {{_ 'edit-avatar'}}
           unless isSandstorm
             if isNotOAuth2AuthenticationMethod
               li
                 a.js-change-password
    -              i.fa.fa-key
    +              | 🔑
                   | {{_ 'changePasswordPopup-title'}}
           li
             a.js-change-language
    -          i.fa.fa-flag
    +          | 🏁
               | {{_ 'changeLanguagePopup-title'}}
    -      if isSupportPageEnabled
    -        li
    -          a(href="{{pathFor 'support'}}")
    -            i.fa.fa-question-circle
    -            | {{_ 'support'}}
    +      //li
    +      //  a.js-support
    +      //    ❓-circle
    +      //    | {{_ 'support'}}
       unless isSandstorm
         hr
         ul.pop-over-list
           li
             a.js-logout
    -          i.fa.fa-sign-out
    +          | 🚪
               | {{_ 'log-out'}}
     
     template(name="invitePeoplePopup")
    @@ -164,8 +161,7 @@ template(name="editProfilePopup")
     template(name="supportPopup")
       ul.pop-over-list
         li
    -      +viewer
    -        = currentSetting.supportPopupText
    +      | Support popup text will be editable later.
     
     template(name="changePasswordPopup")
       +atForm(state='changePwd')
    @@ -175,28 +171,27 @@ template(name="changeLanguagePopup")
         each languages
           li(class="{{# if isCurrentLanguage}}active{{/if}}")
             a.js-set-language
    -          span.emoji-icon {{languageFlag}}
    +          | {{languageFlag}}
               | {{name}}
    -          if rtl
    -            |  (RTL)
               if isCurrentLanguage
    -            i.fa.fa-check
    +            | ✅
    +
     template(name="changeSettingsPopup")
       ul.pop-over-list
    -    li
    -      a.js-toggle-desktop-drag-handles
    -        i.fa.fa-arrows
    -        | {{_ 'show-desktop-drag-handles'}}
    -        if isShowDesktopDragHandles
    -          i.fa.fa-check
    +    //li
    +    //  a.js-toggle-desktop-drag-handles
    +    //    i.fa.fa-arrows
    +    //    | {{_ 'show-desktop-drag-handles'}}
    +    //    if isShowDesktopDragHandles
    +    //      i.fa.fa-check
         unless currentUser.isWorker
           li
             label.bold.clear
    -          i.fa.fa-sort-numeric-asc
    +          | 🔢
               | {{_ 'show-cards-minimum-count'}}
             input#show-cards-count-at.inline-input.left(type="number" value="#{showCardsCountAt}" min="-1")
             label.bold.clear
    -          i.fa.fa-calendar
    +          | 📅
               | {{_ 'start-day-of-week'}}
             select#start-day-of-week.inline-input.left
               each day in weekDays startDayOfWeek
    diff --git a/client/components/users/userHeader.js b/client/components/users/userHeader.js
    index 0054ab12e..5514a1127 100644
    --- a/client/components/users/userHeader.js
    +++ b/client/components/users/userHeader.js
    @@ -1,15 +1,16 @@
     import { ReactiveCache } from '/imports/reactiveCache';
     import { TAPi18n } from '/imports/i18n';
    -import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
     
     Template.headerUserBar.events({
       'click .js-open-header-member-menu': Popup.open('memberMenu'),
       'click .js-change-avatar': Popup.open('changeAvatar'),
     });
     
    -Template.memberMenuPopup.onCreated(function () {
    -  Meteor.subscribe('setting');
    -});
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    Meteor.subscribe('setting');
    +  },
    +}).register('memberMenuPopup');
     
     Template.memberMenuPopup.helpers({
       templatesBoardId() {
    @@ -30,10 +31,6 @@ Template.memberMenuPopup.helpers({
           return false;
         }
       },
    -  isSupportPageEnabled() {
    -    const setting = ReactiveCache.getCurrentSetting();
    -    return setting && setting.supportPageEnabled;
    -  },
       isSameDomainNameSettingValue(){
         const currSett = ReactiveCache.getCurrentSetting();
         if(currSett && currSett != undefined && currSett.disableRegistration && currSett.mailDomainName !== undefined && currSett.mailDomainName != ""){
    @@ -93,15 +90,6 @@ Template.memberMenuPopup.events({
       'click .js-notifications-drawer-toggle'() {
         Session.set('showNotificationsDrawer', !Session.get('showNotificationsDrawer'));
       },
    -  'click .js-toggle-grey-icons'(event) {
    -    event.preventDefault();
    -    const currentUser = ReactiveCache.getCurrentUser();
    -    if (!currentUser || !Meteor.userId()) return;
    -    const current = (currentUser.profile && currentUser.profile.GreyIcons) || false;
    -    Meteor.call('toggleGreyIcons', (err) => {
    -      if (err && process.env.DEBUG === 'true') console.error('toggleGreyIcons error', err);
    -    });
    -  },
       'click .js-logout'(event) {
         event.preventDefault();
     
    @@ -112,6 +100,12 @@ Template.memberMenuPopup.events({
       },
     });
     
    +BlazeComponent.extendComponent({
    +  onCreated() {
    +    Meteor.subscribe('setting');
    +  },
    +}).register('editProfilePopup');
    +
     Template.invitePeoplePopup.events({
       'click a.js-toggle-board-choose'(event){
         let target = $(event.target);
    @@ -160,23 +154,33 @@ Template.invitePeoplePopup.events({
       },
     });
     
    -Template.editProfilePopup.onCreated(function() {
    -  Meteor.subscribe('setting');
    -  this.subscribe('accountSettings');
    -});
    -
     Template.editProfilePopup.helpers({
       allowEmailChange() {
    -    const setting = AccountSettings.findOne('accounts-allowEmailChange');
    -    return setting && setting.booleanValue;
    +    Meteor.call('AccountSettings.allowEmailChange', (_, result) => {
    +      if (result) {
    +        return true;
    +      } else {
    +        return false;
    +      }
    +    });
       },
       allowUserNameChange() {
    -    const setting = AccountSettings.findOne('accounts-allowUserNameChange');
    -    return setting && setting.booleanValue;
    +    Meteor.call('AccountSettings.allowUserNameChange', (_, result) => {
    +      if (result) {
    +        return true;
    +      } else {
    +        return false;
    +      }
    +    });
       },
       allowUserDelete() {
    -    const setting = AccountSettings.findOne('accounts-allowUserDelete');
    -    return setting && setting.booleanValue;
    +    Meteor.call('AccountSettings.allowUserDelete', (_, result) => {
    +      if (result) {
    +        return true;
    +      } else {
    +        return false;
    +      }
    +    });
       },
     });
     
    @@ -277,7 +281,7 @@ Template.changePasswordPopup.onRendered(function() {
     Template.changeLanguagePopup.helpers({
       languages() {
         return TAPi18n.getSupportedLanguages()
    -      .map(({ tag, name, rtl }) => ({ tag, name, rtl }))
    +      .map(({ tag, name }) => ({ tag: tag, name }))
           .sort((a, b) => {
             if (a.name === b.name) {
               return 0;
    diff --git a/client/config/blazeHelpers.js b/client/config/blazeHelpers.js
    index 6d74b658e..967b83059 100644
    --- a/client/config/blazeHelpers.js
    +++ b/client/config/blazeHelpers.js
    @@ -1,31 +1,25 @@
     import { ReactiveCache } from '/imports/reactiveCache';
    -import {
    -  DEFAULT_ASSETLINKS,
    -  DEFAULT_HEAD_LINKS,
    -  DEFAULT_HEAD_META,
    -  DEFAULT_SITE_MANIFEST,
    -} from '/imports/lib/customHeadDefaults';
     import { Blaze } from 'meteor/blaze';
     import { Session } from 'meteor/session';
    -import {
    -  formatDateTime,
    -  formatDate,
    -  formatTime,
    -  getISOWeek,
    -  isValidDate,
    -  isBefore,
    -  isAfter,
    -  isSame,
    -  add,
    -  subtract,
    -  startOf,
    -  endOf,
    -  format,
    -  parseDate,
    -  now,
    -  createDate,
    -  fromNow,
    -  calendar,
    +import { 
    +  formatDateTime, 
    +  formatDate, 
    +  formatTime, 
    +  getISOWeek, 
    +  isValidDate, 
    +  isBefore, 
    +  isAfter, 
    +  isSame, 
    +  add, 
    +  subtract, 
    +  startOf, 
    +  endOf, 
    +  format, 
    +  parseDate, 
    +  now, 
    +  createDate, 
    +  fromNow, 
    +  calendar 
     } from '/imports/lib/dateUtils';
     
     Blaze.registerHelper('currentBoard', () => {
    @@ -48,44 +42,12 @@ Blaze.registerHelper('currentSetting', () => {
       return ret;
     });
     
    -Blaze.registerHelper('customHeadMetaTagsValue', () => {
    -  const setting = ReactiveCache.getCurrentSetting();
    -  if (setting && typeof setting.customHeadMetaTags === 'string') {
    -    return setting.customHeadMetaTags;
    -  }
    -  return DEFAULT_HEAD_META;
    -});
    -
    -Blaze.registerHelper('customHeadLinkTagsValue', () => {
    -  const setting = ReactiveCache.getCurrentSetting();
    -  if (setting && typeof setting.customHeadLinkTags === 'string') {
    -    return setting.customHeadLinkTags;
    -  }
    -  return DEFAULT_HEAD_LINKS;
    -});
    -
    -Blaze.registerHelper('customManifestContentValue', () => {
    -  const setting = ReactiveCache.getCurrentSetting();
    -  if (setting && typeof setting.customManifestContent === 'string') {
    -    return setting.customManifestContent;
    -  }
    -  return DEFAULT_SITE_MANIFEST;
    -});
    -
    -Blaze.registerHelper('customAssetLinksContentValue', () => {
    -  const setting = ReactiveCache.getCurrentSetting();
    -  if (setting && typeof setting.customAssetLinksContent === 'string') {
    -    return setting.customAssetLinksContent;
    -  }
    -  return DEFAULT_ASSETLINKS;
    -});
    -
     Blaze.registerHelper('currentUser', () => {
       const ret = ReactiveCache.getCurrentUser();
       return ret;
     });
     
    -Blaze.registerHelper('getUser', (userId) => ReactiveCache.getUser(userId));
    +Blaze.registerHelper('getUser', userId => ReactiveCache.getUser(userId));
     
     Blaze.registerHelper('concat', (...args) => args.slice(0, -1).join(''));
     
    @@ -101,18 +63,16 @@ Blaze.registerHelper('isTouchScreenOrShowDesktopDragHandles', () =>
       Utils.isTouchScreenOrShowDesktopDragHandles(),
     );
     
    -Blaze.registerHelper('displayDate', (...args) => {
    +Blaze.registerHelper('moment', (...args) => {
       args.pop(); // hash
       const [date, formatStr] = args;
       return format(new Date(date), formatStr ?? 'LLLL');
     });
     
    -Blaze.registerHelper('canModifyCard', () => Utils.canModifyCard());
    +Blaze.registerHelper('canModifyCard', () =>
    +  Utils.canModifyCard(),
    +);
     
    -Blaze.registerHelper('canMoveCard', () => Utils.canMoveCard());
    -
    -Blaze.registerHelper('canModifyBoard', () => Utils.canModifyBoard());
    -
    -Blaze.registerHelper('add', (a, b) => a + b);
    -
    -Blaze.registerHelper('increment', (n) => (n || 0) + 1);
    +Blaze.registerHelper('canModifyBoard', () =>
    +  Utils.canModifyBoard(),
    +);
    diff --git a/client/config/reactiveTabs.js b/client/config/reactiveTabs.js
    new file mode 100644
    index 000000000..5365c6d13
    --- /dev/null
    +++ b/client/config/reactiveTabs.js
    @@ -0,0 +1,4 @@
    +// XXX Since Blaze doesn't have a clean high component API, component API are
    +// also tweaky to use. I guess React would be a solution.
    +const template = 'basicTabs';
    +ReactiveTabs.createInterface({ template });
    diff --git a/client/lib/attachmentMigrationManager.js b/client/lib/attachmentMigrationManager.js
    index f4f385d84..e84124612 100644
    --- a/client/lib/attachmentMigrationManager.js
    +++ b/client/lib/attachmentMigrationManager.js
    @@ -5,9 +5,7 @@
      */
     
     import { ReactiveVar } from 'meteor/reactive-var';
    -import { Tracker } from 'meteor/tracker';
     import { ReactiveCache } from '/imports/reactiveCache';
    -import { AttachmentMigrationStatus } from '/imports/attachmentMigrationClient';
     
     // Reactive variables for attachment migration progress
     export const attachmentMigrationProgress = new ReactiveVar(0);
    @@ -39,8 +37,8 @@ class AttachmentMigrationManager {
           if (!attachment) return false;
     
           // Check if attachment has old structure (no meta field or missing required fields)
    -      return !attachment.meta ||
    -             !attachment.meta.cardId ||
    +      return !attachment.meta || 
    +             !attachment.meta.cardId || 
                  !attachment.meta.boardId ||
                  !attachment.meta.listId;
         } catch (error) {
    @@ -226,41 +224,6 @@ class AttachmentMigrationManager {
     
     export const attachmentMigrationManager = new AttachmentMigrationManager();
     
    -// Setup pub/sub for attachment migration status
    -if (Meteor.isClient) {
    -  // Subscribe to all attachment migration statuses when component is active
    -  // This will be called by board components when they need migration status
    -  window.subscribeToAttachmentMigrationStatus = function(boardId) {
    -    return Meteor.subscribe('attachmentMigrationStatus', boardId);
    -  };
    -
    -  // Reactive tracking of migration status from published collection
    -  Tracker.autorun(() => {
    -    const statuses = AttachmentMigrationStatus.find({}).fetch();
    -
    -    statuses.forEach(status => {
    -      if (status.isMigrated) {
    -        globalMigratedBoards.add(status.boardId);
    -        attachmentMigrationManager.migratedBoards.add(status.boardId);
    -      }
    -    });
    -
    -    // Update UI reactive variables based on active migration
    -    const activeMigration = AttachmentMigrationStatus.findOne({
    -      status: { $in: ['migrating', 'pending'] }
    -    });
    -
    -    if (activeMigration) {
    -      isMigratingAttachments.set(true);
    -      attachmentMigrationProgress.set(activeMigration.progress || 0);
    -      attachmentMigrationStatus.set(activeMigration.status || '');
    -    } else {
    -      isMigratingAttachments.set(false);
    -    }
    -  });
    -}
    -
    -
     
     
     
    diff --git a/client/lib/autofocus.js b/client/lib/autofocus.js
    deleted file mode 100644
    index cfe26d905..000000000
    --- a/client/lib/autofocus.js
    +++ /dev/null
    @@ -1,56 +0,0 @@
    -// Native replacement for mquandalle:autofocus package
    -// Handles autofocus attribute in dynamically rendered Blaze templates
    -import { Meteor } from 'meteor/meteor';
    -import { Tracker } from 'meteor/tracker';
    -
    -// Use MutationObserver to watch for elements with autofocus attribute
    -Meteor.startup(() => {
    -  // Function to focus autofocus elements
    -  const focusAutofocusElements = () => {
    -    const elements = document.querySelectorAll('[autofocus]');
    -    if (elements.length > 0) {
    -      // Focus the last one (most recently added)
    -      const el = elements[elements.length - 1];
    -      if (el && typeof el.focus === 'function' && document.activeElement !== el) {
    -        setTimeout(() => {
    -          el.focus();
    -          // For textareas and text inputs, also select the content if it exists
    -          if ((el.tagName === 'TEXTAREA' || (el.tagName === 'INPUT' && el.type === 'text')) && el.value) {
    -            el.select();
    -          }
    -        }, 50);
    -      }
    -    }
    -  };
    -
    -  // Watch for DOM changes
    -  const observer = new MutationObserver((mutations) => {
    -    let shouldFocus = false;
    -    mutations.forEach((mutation) => {
    -      mutation.addedNodes.forEach((node) => {
    -        if (node.nodeType === 1) { // Element node
    -          if (node.hasAttribute && node.hasAttribute('autofocus')) {
    -            shouldFocus = true;
    -          } else if (node.querySelector) {
    -            const autofocusChild = node.querySelector('[autofocus]');
    -            if (autofocusChild) {
    -              shouldFocus = true;
    -            }
    -          }
    -        }
    -      });
    -    });
    -    if (shouldFocus) {
    -      Tracker.afterFlush(focusAutofocusElements);
    -    }
    -  });
    -
    -  // Start observing
    -  observer.observe(document.body, {
    -    childList: true,
    -    subtree: true,
    -  });
    -
    -  // Also handle initial autofocus elements
    -  Tracker.afterFlush(focusAutofocusElements);
    -});
    diff --git a/client/lib/boardConverter.js b/client/lib/boardConverter.js
    index 68b8c7d72..71bbe5622 100644
    --- a/client/lib/boardConverter.js
    +++ b/client/lib/boardConverter.js
    @@ -113,7 +113,7 @@ class BoardConverter {
           }
     
           conversionStatus.set(`Converting ${listsToConvert.length} lists...`);
    -
    +      
           const startTime = Date.now();
           const totalLists = listsToConvert.length;
           let convertedLists = 0;
    @@ -122,20 +122,20 @@ class BoardConverter {
           const batchSize = 10;
           for (let i = 0; i < listsToConvert.length; i += batchSize) {
             const batch = listsToConvert.slice(i, i + batchSize);
    -
    +        
             // Process batch
             await this.processBatch(batch, defaultSwimlane._id);
    -
    +        
             convertedLists += batch.length;
             const progress = Math.round((convertedLists / totalLists) * 100);
             conversionProgress.set(progress);
    -
    +        
             // Calculate estimated time remaining
             const elapsed = Date.now() - startTime;
             const rate = convertedLists / elapsed; // lists per millisecond
             const remaining = totalLists - convertedLists;
             const estimatedMs = remaining / rate;
    -
    +        
             conversionStatus.set(`Converting list ${convertedLists} of ${totalLists}...`);
             conversionEstimatedTime.set(this.formatTime(estimatedMs));
     
    @@ -146,11 +146,11 @@ class BoardConverter {
           // Mark as converted
           this.conversionCache.set(boardId, true);
           globalConvertedBoards.add(boardId); // Mark board as converted
    -
    +      
           conversionStatus.set('Board conversion completed!');
           conversionProgress.set(100);
           console.log(`Board ${boardId} conversion completed and marked as converted`);
    -
    +      
           // Clear status after a delay
           setTimeout(() => {
             isConverting.set(false);
    diff --git a/client/lib/boardMultiSelection.js b/client/lib/boardMultiSelection.js
    deleted file mode 100644
    index 036c312b6..000000000
    --- a/client/lib/boardMultiSelection.js
    +++ /dev/null
    @@ -1,73 +0,0 @@
    -import { ReactiveCache } from '/imports/reactiveCache';
    -
    -BoardMultiSelection = {
    -  _selectedBoards: new ReactiveVar([]),
    -
    -  _isActive: new ReactiveVar(false),
    -
    -  reset() {
    -    this._selectedBoards.set([]);
    -  },
    -
    -  isActive() {
    -    return this._isActive.get();
    -  },
    -
    -  count() {
    -    return this._selectedBoards.get().length;
    -  },
    -
    -  isEmpty() {
    -    return this.count() === 0;
    -  },
    -
    -  getSelectedBoardIds() {
    -    return this._selectedBoards.get();
    -  },
    -
    -  activate() {
    -    if (!this.isActive()) {
    -      this._isActive.set(true);
    -      Tracker.flush();
    -    }
    -  },
    -
    -  disable() {
    -    if (this.isActive()) {
    -      this._isActive.set(false);
    -      this.reset();
    -    }
    -  },
    -
    -  add(boardIds) {
    -    return this.toggle(boardIds, { add: true, remove: false });
    -  },
    -
    -  remove(boardIds) {
    -    return this.toggle(boardIds, { add: false, remove: true });
    -  },
    -
    -  toogle(boardIds) {
    -    return this.toggle(boardIds, { add: true, remove: true });
    -  },
    -
    -  toggle(boardIds, { add, remove } = {}) {
    -    boardIds = _.isString(boardIds) ? [boardIds] : boardIds;
    -    let selectedBoards = this._selectedBoards.get();
    -
    -    boardIds.forEach(boardId => {
    -      const index = selectedBoards.indexOf(boardId);
    -      if (index > -1 && remove) {
    -        selectedBoards = selectedBoards.filter(id => id !== boardId);
    -      } else if (index === -1 && add) {
    -        selectedBoards.push(boardId);
    -      }
    -    });
    -
    -    this._selectedBoards.set(selectedBoards);
    -  },
    -
    -  isSelected(boardId) {
    -    return this._selectedBoards.get().includes(boardId);
    -  },
    -};
    diff --git a/client/lib/cardSearch.js b/client/lib/cardSearch.js
    index c2e9df3a0..a143965cf 100644
    --- a/client/lib/cardSearch.js
    +++ b/client/lib/cardSearch.js
    @@ -5,11 +5,8 @@ import SessionData from '../../models/usersessiondata';
     import {QueryDebug} from "../../config/query-classes";
     import {OPERATOR_DEBUG} from "../../config/search-const";
     
    -// Plain helper class for search pages with pagination.
    -// Not a BlazeComponent; instantiated in each template's onCreated.
    -export class CardSearchPaged {
    -  constructor(templateInstance) {
    -    this.tpl = templateInstance;
    +export class CardSearchPagedComponent extends BlazeComponent {
    +  onCreated() {
         this.searching = new ReactiveVar(false);
         this.hasResults = new ReactiveVar(false);
         this.hasQueryErrors = new ReactiveVar(false);
    @@ -36,24 +33,24 @@ export class CardSearchPaged {
               console.log('Subscription ready, getting results...');
               console.log('Subscription ready - sessionId:', that.sessionId);
             }
    -
    +        
             // Wait for session data to be available (with timeout)
             let waitCount = 0;
             const maxWaitCount = 50; // 10 seconds max wait
    -
    +        
             const waitForSessionData = () => {
               waitCount++;
               const sessionData = that.getSessionData();
               if (process.env.DEBUG === 'true') {
                 console.log('waitForSessionData - attempt', waitCount, 'session data:', sessionData);
               }
    -
    +          
               if (sessionData) {
                 const results = that.getResults();
                 if (process.env.DEBUG === 'true') {
                   console.log('Search results count:', results ? results.length : 0);
                 }
    -
    +            
                 // If no results and this is a due cards search, try to retry
                 if ((!results || results.length === 0) && that.searchRetryCount !== undefined && that.searchRetryCount < that.maxRetries) {
                   if (process.env.DEBUG === 'true') {
    @@ -67,7 +64,7 @@ export class CardSearchPaged {
                   }, 500);
                   return;
                 }
    -
    +            
                 that.searching.set(false);
                 that.hasResults.set(true);
                 that.serverError.set(false);
    @@ -86,7 +83,7 @@ export class CardSearchPaged {
                    if (process.env.DEBUG === 'true') {
                      console.log('Fallback search results count:', results ? results.length : 0);
                    }
    -
    +               
                    if (results && results.length > 0) {
                      that.searching.set(false);
                      that.hasResults.set(true);
    @@ -98,7 +95,7 @@ export class CardSearchPaged {
                    }
                  }
             };
    -
    +        
             // Start waiting for session data
             Meteor.setTimeout(waitForSessionData, 100);
           },
    @@ -134,7 +131,7 @@ export class CardSearchPaged {
         if (process.env.DEBUG === 'true') {
           console.log('getSessionData - looking for sessionId:', sessionIdToUse);
         }
    -
    +    
         // Try using the raw SessionData collection instead of ReactiveCache
         const sessionData = SessionData.findOne({
           sessionId: sessionIdToUse,
    @@ -142,7 +139,7 @@ export class CardSearchPaged {
         if (process.env.DEBUG === 'true') {
           console.log('getSessionData - found session data (raw):', sessionData);
         }
    -
    +    
         // Also try ReactiveCache for comparison
         const reactiveSessionData = ReactiveCache.getSessionData({
           sessionId: sessionIdToUse,
    @@ -150,7 +147,7 @@ export class CardSearchPaged {
         if (process.env.DEBUG === 'true') {
           console.log('getSessionData - found session data (reactive):', reactiveSessionData);
         }
    -
    +    
         return sessionData || reactiveSessionData;
       }
     
    @@ -164,7 +161,7 @@ export class CardSearchPaged {
           console.log('getResults - session data:', this.sessionData);
         }
         const cards = [];
    -
    +    
         if (this.sessionData && this.sessionData.cards) {
           if (process.env.DEBUG === 'true') {
             console.log('getResults - cards array length:', this.sessionData.cards.length);
    @@ -195,7 +192,7 @@ export class CardSearchPaged {
           if (process.env.DEBUG === 'true') {
             console.log('getResults - direct card search found:', allCards ? allCards.length : 0, 'cards');
           }
    -
    +      
           if (allCards && allCards.length > 0) {
             allCards.forEach(card => {
               if (card && card._id) {
    @@ -203,7 +200,7 @@ export class CardSearchPaged {
               }
             });
           }
    -
    +      
           this.queryErrors = [];
         }
         if (this.queryErrors.length) {
    @@ -270,7 +267,7 @@ export class CardSearchPaged {
           queryParams.text,
           this.subscriptionCallbacks,
         );
    -
    +    
         const sessionDataHandle = Meteor.subscribe('sessionData', this.sessionId);
         if (process.env.DEBUG === 'true') {
           console.log('Subscribed to sessionData with sessionId:', this.sessionId);
    @@ -340,4 +337,19 @@ export class CardSearchPaged {
         const baseUrl = window.location.href.replace(/([?#].*$|\s*$)/, '');
         return `${baseUrl}?q=${encodeURIComponent(this.query.get())}`;
       }
    +
    +  events() {
    +    return [
    +      {
    +        'click .js-next-page'(evt) {
    +          evt.preventDefault();
    +          this.nextPage();
    +        },
    +        'click .js-previous-page'(evt) {
    +          evt.preventDefault();
    +          this.previousPage();
    +        },
    +      },
    +    ];
    +  }
     }
    diff --git a/client/lib/currentCard.js b/client/lib/currentCard.js
    deleted file mode 100644
    index 7b33d5096..000000000
    --- a/client/lib/currentCard.js
    +++ /dev/null
    @@ -1,117 +0,0 @@
    -import Cards from '/models/cards';
    -
    -function getCardIdFromData(data) {
    -  if (!data || !data._id) {
    -    return null;
    -  }
    -
    -  if (
    -    data.boardId ||
    -    data.listId ||
    -    typeof data.absoluteUrl === 'function' ||
    -    typeof data.getTitle === 'function'
    -  ) {
    -    return data._id;
    -  }
    -
    -  return null;
    -}
    -
    -function getCardIdFromElement(element) {
    -  if (!element || typeof element.closest !== 'function') {
    -    return null;
    -  }
    -
    -  const cardDetails = element.closest('.js-card-details');
    -  if (!cardDetails) {
    -    return null;
    -  }
    -
    -  return getCardIdFromData(Blaze.getData(cardDetails));
    -}
    -
    -function getCardIdFromParentData(maxDepth = 8) {
    -  for (let depth = 1; depth <= maxDepth; depth += 1) {
    -    try {
    -      const cardId = getCardIdFromData(Template.parentData(depth));
    -      if (cardId) {
    -        return cardId;
    -      }
    -    } catch (error) {
    -      break;
    -    }
    -  }
    -
    -  return null;
    -}
    -
    -function getPopupStack() {
    -  if (typeof Popup !== 'undefined' && typeof Popup._getTopStack === 'function') {
    -    return Popup._getTopStack();
    -  }
    -
    -  return null;
    -}
    -
    -export function getCurrentCardIdFromContext({ ignorePopupCard = false } = {}) {
    -  let cardId;
    -
    -  try {
    -    cardId = getCardIdFromData(Template.currentData());
    -    if (cardId) {
    -      return cardId;
    -    }
    -  } catch (error) {
    -    // No active Blaze view.
    -  }
    -
    -  cardId = getCardIdFromParentData();
    -  if (cardId) {
    -    return cardId;
    -  }
    -
    -  const popupStack = getPopupStack();
    -
    -  cardId = getCardIdFromData(popupStack?.dataContext);
    -  if (cardId) {
    -    return cardId;
    -  }
    -
    -  cardId = getCardIdFromElement(popupStack?.openerElement);
    -  if (cardId) {
    -    return cardId;
    -  }
    -
    -  cardId = getCardIdFromElement(document.activeElement);
    -  if (cardId) {
    -    return cardId;
    -  }
    -
    -  cardId = Session.get('currentCard');
    -  if (cardId) {
    -    return cardId;
    -  }
    -
    -  if (!ignorePopupCard) {
    -    cardId = Session.get('popupCardId');
    -    if (cardId) {
    -      return cardId;
    -    }
    -  }
    -
    -  const cardDetails = document.querySelectorAll('.js-card-details');
    -  if (cardDetails.length === 1) {
    -    return getCardIdFromElement(cardDetails[0]);
    -  }
    -
    -  return null;
    -}
    -
    -export function getCurrentCardFromContext(options) {
    -  const cardId = getCurrentCardIdFromContext(options);
    -  if (!cardId) {
    -    return null;
    -  }
    -
    -  return Cards.findOne(cardId);
    -}
    diff --git a/client/lib/datepicker.js b/client/lib/datepicker.js
    index 7b4d031f9..6a4f010e9 100644
    --- a/client/lib/datepicker.js
    +++ b/client/lib/datepicker.js
    @@ -1,170 +1,221 @@
     import { ReactiveCache } from '/imports/reactiveCache';
    -import { getCurrentCardFromContext } from '/client/lib/currentCard';
    +import { TAPi18n } from '/imports/i18n';
    +import { 
    +  formatDateTime, 
    +  formatDate, 
    +  formatDateByUserPreference,
    +  formatTime, 
    +  getISOWeek, 
    +  isValidDate, 
    +  isBefore, 
    +  isAfter, 
    +  isSame, 
    +  add, 
    +  subtract, 
    +  startOf, 
    +  endOf, 
    +  format, 
    +  parseDate, 
    +  now, 
    +  createDate, 
    +  fromNow, 
    +  calendar 
    +} from '/imports/lib/dateUtils';
     
    -// Helper to check if a date is valid
    -function isValidDate(date) {
    -  return date instanceof Date && !isNaN(date);
    +// Helper function to get time format for 24 hours
    +function adjustedTimeFormat() {
    +  return 'HH:mm';
     }
     
    -// Format date as YYYY-MM-DD
    -function formatDate(date) {
    -  if (!isValidDate(date)) return '';
    -  const year = date.getFullYear();
    -  const month = String(date.getMonth() + 1).padStart(2, '0');
    -  const day = String(date.getDate()).padStart(2, '0');
    -  return `${year}-${month}-${day}`;
    -}
    +//   .replace(/HH/i, 'H');
     
    -// Format time as HH:mm
    -function formatTime(date) {
    -  if (!isValidDate(date)) return '';
    -  const hours = String(date.getHours()).padStart(2, '0');
    -  const minutes = String(date.getMinutes()).padStart(2, '0');
    -  return `${hours}:${minutes}`;
    -}
    +export class DatePicker extends BlazeComponent {
    +  template() {
    +    return 'datepicker';
    +  }
     
    -/**
    - * Sets up datepicker state on a template instance.
    - * Call from onCreated. Stores state on tpl.datePicker.
    - *
    - * @param {TemplateInstance} tpl - The Blaze template instance
    - * @param {Object} options
    - * @param {string} [options.defaultTime='1970-01-01 08:00:00'] - Default time string
    - * @param {Date} [options.initialDate] - Initial date to set (if valid)
    - */
    -export function setupDatePicker(tpl, { defaultTime = '1970-01-01 08:00:00', initialDate } = {}) {
    -  const card = getCurrentCardFromContext() || Template.currentData();
    -  tpl.datePicker = {
    -    error: new ReactiveVar(''),
    -    card,
    -    date: new ReactiveVar(initialDate && isValidDate(new Date(initialDate)) ? new Date(initialDate) : new Date('invalid')),
    -    defaultTime,
    -  };
    -}
    +  onCreated(defaultTime = '1970-01-01 08:00:00') {
    +    this.error = new ReactiveVar('');
    +    this.card = this.data();
    +    this.date = new ReactiveVar(new Date('invalid'));
    +    this.defaultTime = defaultTime;
    +  }
     
    -/**
    - * onRendered logic for datepicker templates.
    - * Sets initial input values from the datePicker state.
    - *
    - * @param {TemplateInstance} tpl - The Blaze template instance
    - */
    -export function datePickerRendered(tpl) {
    -  const dp = tpl.datePicker;
    -  if (isValidDate(dp.date.get())) {
    -    const dateInput = tpl.find('#date');
    -    const timeInput = tpl.find('#time');
    -
    -    if (dateInput) {
    -      dateInput.value = formatDate(dp.date.get());
    -    }
    -    if (timeInput && !timeInput.value && dp.defaultTime) {
    -      const defaultDate = new Date(dp.defaultTime);
    -      timeInput.value = formatTime(defaultDate);
    -    } else if (timeInput && isValidDate(dp.date.get())) {
    -      timeInput.value = formatTime(dp.date.get());
    +  startDayOfWeek() {
    +    const currentUser = ReactiveCache.getCurrentUser();
    +    if (currentUser) {
    +      return currentUser.getStartDayOfWeek();
    +    } else {
    +      return 1;
         }
       }
    -}
     
    -/**
    - * Returns helpers object for datepicker templates.
    - * All helpers read from Template.instance().datePicker.
    - */
    -export function datePickerHelpers() {
    -  return {
    -    error() {
    -      return Template.instance().datePicker.error;
    -    },
    -    showDate() {
    -      const dp = Template.instance().datePicker;
    -      if (isValidDate(dp.date.get())) return formatDate(dp.date.get());
    -      return '';
    -    },
    -    showTime() {
    -      const dp = Template.instance().datePicker;
    -      if (isValidDate(dp.date.get())) return formatTime(dp.date.get());
    -      return '';
    -    },
    -    dateFormat() {
    -      return 'YYYY-MM-DD';
    -    },
    -    timeFormat() {
    -      return 'HH:mm';
    -    },
    -    startDayOfWeek() {
    +  onRendered() {
    +    // Set initial values for text and time inputs
    +    if (isValidDate(this.date.get())) {
    +      const dateInput = this.find('#date');
    +      const timeInput = this.find('#time');
    +      
    +      if (dateInput) {
    +        // Use user's preferred format for text input
    +        const currentUser = ReactiveCache.getCurrentUser();
    +        const userFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
    +        dateInput.value = formatDateByUserPreference(this.date.get(), userFormat, false);
    +      }
    +      if (timeInput) {
    +        if (!timeInput.value && this.defaultTime) {
    +          const defaultDate = new Date(this.defaultTime);
    +          timeInput.value = formatTime(defaultDate);
    +        } else if (isValidDate(this.date.get())) {
    +          timeInput.value = formatTime(this.date.get());
    +        }
    +      }
    +    }
    +  }
    +
    +  showDate() {
    +    if (isValidDate(this.date.get())) {
    +      // Use user's preferred format for display, but HTML date input needs YYYY-MM-DD
           const currentUser = ReactiveCache.getCurrentUser();
    -      if (currentUser) {
    -        return currentUser.getStartDayOfWeek();
    -      } else {
    -        return 1;
    -      }
    -    },
    -  };
    -}
    -
    -/**
    - * Returns events object for datepicker templates.
    - *
    - * @param {Object} callbacks
    - * @param {Function} callbacks.storeDate - Called with (date) when form is submitted
    - * @param {Function} callbacks.deleteDate - Called when delete button is clicked
    - */
    -export function datePickerEvents({ storeDate, deleteDate }) {
    -  return {
    -    'change .js-date-field'(evt, tpl) {
    -      // Native HTML date input validation
    -      const dateValue = tpl.find('#date').value;
    -      if (dateValue) {
    -        // HTML date input format is always YYYY-MM-DD
    -        const dateObj = new Date(dateValue + 'T12:00:00');
    -        if (isValidDate(dateObj)) {
    -          tpl.datePicker.error.set('');
    -        } else {
    -          tpl.datePicker.error.set('invalid-date');
    -        }
    -      }
    -    },
    -    'change .js-time-field'(evt, tpl) {
    -      // Native HTML time input validation
    -      const timeValue = tpl.find('#time').value;
    -      if (timeValue) {
    -        // HTML time input format is always HH:mm
    -        const timeObj = new Date(`1970-01-01T${timeValue}:00`);
    -        if (isValidDate(timeObj)) {
    -          tpl.datePicker.error.set('');
    -        } else {
    -          tpl.datePicker.error.set('invalid-time');
    -        }
    -      }
    -    },
    -    'submit .edit-date'(evt, tpl) {
    -      evt.preventDefault();
    -
    -      const dateValue = evt.target.date.value;
    -      const timeValue = evt.target.time.value || '12:00'; // Default to 12:00 if no time given
    -
    -      if (!dateValue) {
    -        tpl.datePicker.error.set('invalid-date');
    -        evt.target.date.focus();
    -        return;
    -      }
    -
    -      // Combine date and time: HTML date input is YYYY-MM-DD, time input is HH:mm
    -      const dateTimeString = `${dateValue}T${timeValue}:00`;
    -      const newCompleteDate = new Date(dateTimeString);
    -
    -      if (!isValidDate(newCompleteDate)) {
    -        tpl.datePicker.error.set('invalid');
    -        return;
    -      }
    -
    -      storeDate.call(tpl, newCompleteDate);
    -      Popup.back();
    -    },
    -    'click .js-delete-date'(evt, tpl) {
    -      evt.preventDefault();
    -      deleteDate.call(tpl);
    -      Popup.back();
    -    },
    -  };
    +      const userFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
    +      return formatDateByUserPreference(this.date.get(), userFormat, false);
    +    }
    +    return '';
    +  }
    +  showTime() {
    +    if (isValidDate(this.date.get())) return formatTime(this.date.get());
    +    return '';
    +  }
    +  dateFormat() {
    +    const currentUser = ReactiveCache.getCurrentUser();
    +    const userFormat = currentUser ? currentUser.getDateFormat() : 'YYYY-MM-DD';
    +    // Convert format to localized placeholder
    +    switch (userFormat) {
    +      case 'DD-MM-YYYY':
    +        return TAPi18n.__('date-format-dd-mm-yyyy') || 'PP-KK-VVVV';
    +      case 'MM-DD-YYYY':
    +        return TAPi18n.__('date-format-mm-dd-yyyy') || 'KK-PP-VVVV';
    +      case 'YYYY-MM-DD':
    +      default:
    +        return TAPi18n.__('date-format-yyyy-mm-dd') || 'VVVV-KK-PP';
    +    }
    +  }
    +  timeFormat() {
    +    return 'LT';
    +  }
    +
    +  events() {
    +    return [
    +      {
    +        'change .js-date-field'() {
    +          // Text input date validation
    +          const dateInput = this.find('#date');
    +          if (!dateInput) return;
    +          
    +          const dateValue = dateInput.value;
    +          if (dateValue) {
    +            // Try to parse different date formats
    +            const formats = [
    +              'YYYY-MM-DD',
    +              'DD-MM-YYYY', 
    +              'MM-DD-YYYY',
    +              'DD/MM/YYYY',
    +              'MM/DD/YYYY',
    +              'DD.MM.YYYY',
    +              'MM.DD.YYYY'
    +            ];
    +            
    +            let parsedDate = null;
    +            for (const format of formats) {
    +              parsedDate = parseDate(dateValue, [format], true);
    +              if (parsedDate) break;
    +            }
    +            
    +            // Fallback to native Date parsing
    +            if (!parsedDate) {
    +              parsedDate = new Date(dateValue);
    +            }
    +
    +            if (isValidDate(parsedDate)) {
    +              this.error.set('');
    +            } else {
    +              this.error.set('invalid-date');
    +            }
    +          }
    +        },
    +        'change .js-time-field'() {
    +          // Native HTML time input validation
    +          const timeInput = this.find('#time');
    +          if (!timeInput) return;
    +          
    +          const timeValue = timeInput.value;
    +          if (timeValue) {
    +            const timeObj = new Date(`1970-01-01T${timeValue}`);
    +            if (isValidDate(timeObj)) {
    +              this.error.set('');
    +            } else {
    +              this.error.set('invalid-time');
    +            }
    +          }
    +        },
    +        'submit .edit-date'(evt) {
    +          evt.preventDefault();
    +
    +          const dateValue = evt.target.date.value;
    +          const timeValue = evt.target.time.value || '12:00'; // Default to 12:00 if no time given
    +          
    +          if (!dateValue) {
    +            this.error.set('invalid-date');
    +            evt.target.date.focus();
    +            return;
    +          }
    +
    +          // Try to parse different date formats
    +          const formats = [
    +            'YYYY-MM-DD',
    +            'DD-MM-YYYY', 
    +            'MM-DD-YYYY',
    +            'DD/MM/YYYY',
    +            'MM/DD/YYYY',
    +            'DD.MM.YYYY',
    +            'MM.DD.YYYY'
    +          ];
    +          
    +          let parsedDate = null;
    +          for (const format of formats) {
    +            parsedDate = parseDate(dateValue, [format], true);
    +            if (parsedDate) break;
    +          }
    +          
    +          // Fallback to native Date parsing
    +          if (!parsedDate) {
    +            parsedDate = new Date(dateValue);
    +          }
    +
    +          if (!isValidDate(parsedDate)) {
    +            this.error.set('invalid');
    +            return;
    +          }
    +
    +          // Combine with time
    +          const timeObj = new Date(`1970-01-01T${timeValue}`);
    +          if (!isValidDate(timeObj)) {
    +            this.error.set('invalid-time');
    +            return;
    +          }
    +
    +          // Set the time on the parsed date
    +          parsedDate.setHours(timeObj.getHours(), timeObj.getMinutes(), 0, 0);
    +
    +          this._storeDate(parsedDate);
    +          Popup.back();
    +        },
    +        'click .js-delete-date'(evt) {
    +          evt.preventDefault();
    +          this._deleteDate();
    +          Popup.back();
    +        },
    +      },
    +    ];
    +  }
     }
    diff --git a/client/lib/dialogWithBoardSwimlaneList.js b/client/lib/dialogWithBoardSwimlaneList.js
    index 4ed7dc7de..77a30225f 100644
    --- a/client/lib/dialogWithBoardSwimlaneList.js
    +++ b/client/lib/dialogWithBoardSwimlaneList.js
    @@ -1,52 +1,37 @@
     import { ReactiveCache } from '/imports/reactiveCache';
    -import { TAPi18n } from '/imports/i18n';
     
    -/**
    - * Helper class for popup dialogs that let users select a board, swimlane, and list.
    - * Not a BlazeComponent — instantiated by each Template's onCreated callback.
    - */
    -export class BoardSwimlaneListDialog {
    -  /**
    -   * @param {Blaze.TemplateInstance} tpl - the template instance
    -   * @param {Object} callbacks
    -   * @param {Function} callbacks.getDialogOptions - returns saved options from card/user
    -   * @param {Function} callbacks.setDone - performs the action (boardId, swimlaneId, listId, options)
    -   * @param {Function} [callbacks.getDefaultOption] - override default option shape
    +export class DialogWithBoardSwimlaneList extends BlazeComponent {
    +  /** returns the card dialog options
    +   * @return Object with properties { boardId, swimlaneId, listId }
        */
    -  constructor(tpl, callbacks = {}) {
    -    this.tpl = tpl;
    -    this._getDialogOptions = callbacks.getDialogOptions || (() => undefined);
    -    this._setDone = callbacks.setDone || (() => {});
    -    if (callbacks.getDefaultOption) {
    -      this.getDefaultOption = callbacks.getDefaultOption;
    -    }
    +  getDialogOptions() {
    +  }
     
    -    this.currentBoardId = Utils.getCurrentBoardId();
    -    this.selectedBoardId = new ReactiveVar(this.currentBoardId);
    -    this.selectedSwimlaneId = new ReactiveVar('');
    -    this.selectedListId = new ReactiveVar('');
    -    this.setOption(this.currentBoardId);
    +  /** list is done
    +   * @param listId the selected list id
    +   * @param options the selected options (Object with properties { boardId, swimlaneId, listId })
    +   */
    +  setDone(listId, options) {
       }
     
       /** get the default options
        * @return the options
        */
    -  getDefaultOption() {
    -    return {
    -      boardId: '',
    -      swimlaneId: '',
    -      listId: '',
    -    };
    +  getDefaultOption(boardId) {
    +    const ret = {
    +      'boardId' : "",
    +      'swimlaneId' : "",
    +      'listId' : "",
    +    }
    +    return ret;
       }
     
    -  /** returns the card dialog options (delegates to callback) */
    -  getDialogOptions() {
    -    return this._getDialogOptions();
    -  }
    -
    -  /** performs the done action (delegates to callback) */
    -  async setDone(...args) {
    -    return this._setDone(...args);
    +  onCreated() {
    +    this.currentBoardId = Utils.getCurrentBoardId();
    +    this.selectedBoardId = new ReactiveVar(this.currentBoardId);
    +    this.selectedSwimlaneId = new ReactiveVar('');
    +    this.selectedListId = new ReactiveVar('');
    +    this.setOption(this.currentBoardId);
       }
     
       /** set the last confirmed dialog field values
    @@ -55,40 +40,27 @@ export class BoardSwimlaneListDialog {
       setOption(boardId) {
         this.cardOption = this.getDefaultOption();
     
    -    const currentOptions = this.getDialogOptions();
    +    let currentOptions = this.getDialogOptions();
         if (currentOptions && boardId && currentOptions[boardId]) {
           this.cardOption = currentOptions[boardId];
    -      if (
    -        this.cardOption.boardId &&
    -        this.cardOption.swimlaneId &&
    -        this.cardOption.listId
    -      ) {
    -        this.selectedBoardId.set(this.cardOption.boardId);
    +      if (this.cardOption.boardId &&
    +          this.cardOption.swimlaneId &&
    +          this.cardOption.listId
    +      )
    +      {
    +        this.selectedBoardId.set(this.cardOption.boardId)
             this.selectedSwimlaneId.set(this.cardOption.swimlaneId);
             this.selectedListId.set(this.cardOption.listId);
           }
         }
         this.getBoardData(this.selectedBoardId.get());
    -    if (
    -      !this.selectedSwimlaneId.get() ||
    -      !ReactiveCache.getSwimlane({
    -        _id: this.selectedSwimlaneId.get(),
    -        boardId: this.selectedBoardId.get(),
    -      })
    -    ) {
    +    if (!this.selectedSwimlaneId.get() || !ReactiveCache.getSwimlane({_id: this.selectedSwimlaneId.get(), boardId: this.selectedBoardId.get()})) {
           this.setFirstSwimlaneId();
         }
    -    if (
    -      !this.selectedListId.get() ||
    -      !ReactiveCache.getList({
    -        _id: this.selectedListId.get(),
    -        boardId: this.selectedBoardId.get(),
    -      })
    -    ) {
    +    if (!this.selectedListId.get() || !ReactiveCache.getList({_id: this.selectedListId.get(), boardId: this.selectedBoardId.get()})) {
           this.setFirstListId();
         }
       }
    -
       /** sets the first swimlane id */
       setFirstSwimlaneId() {
         try {
    @@ -97,60 +69,45 @@ export class BoardSwimlaneListDialog {
           this.selectedSwimlaneId.set(swimlaneId);
         } catch (e) {}
       }
    -
       /** sets the first list id */
       setFirstListId() {
         try {
    -      const boardId = this.selectedBoardId.get();
    -      const swimlaneId = this.selectedSwimlaneId.get();
    -      const lists = this.getListsForBoardSwimlane(boardId, swimlaneId);
    -      const listId = lists[0] ? lists[0]._id : '';
    +      const board = ReactiveCache.getBoard(this.selectedBoardId.get());
    +      const listId = board.lists()[0]._id;
           this.selectedListId.set(listId);
         } catch (e) {}
       }
     
    -  /** get lists filtered by board and swimlane */
    -  getListsForBoardSwimlane(boardId, swimlaneId) {
    -    if (!boardId) return [];
    -    const board = ReactiveCache.getBoard(boardId);
    -    if (!board) return [];
    -
    -    const selector = {
    -      boardId,
    -      archived: false,
    -    };
    -
    -    if (swimlaneId) {
    -      const defaultSwimlane =
    -        board.getDefaultSwimline && board.getDefaultSwimline();
    -      if (defaultSwimlane && defaultSwimlane._id === swimlaneId) {
    -        selector.swimlaneId = { $in: [swimlaneId, null, ''] };
    -      } else {
    -        selector.swimlaneId = swimlaneId;
    -      }
    -    }
    -
    -    return ReactiveCache.getLists(selector, { sort: { sort: 1 } });
    -  }
    -
    -  /** returns if the board id was the last confirmed one */
    +  /** returns if the board id was the last confirmed one
    +   * @param boardId check this board id
    +   * @return if the board id was the last confirmed one
    +   */
       isDialogOptionBoardId(boardId) {
    -    return this.cardOption.boardId == boardId;
    +    let ret = this.cardOption.boardId == boardId;
    +    return ret;
       }
     
    -  /** returns if the swimlane id was the last confirmed one */
    +  /** returns if the swimlane id was the last confirmed one
    +   * @param swimlaneId check this swimlane id
    +   * @return if the swimlane id was the last confirmed one
    +   */
       isDialogOptionSwimlaneId(swimlaneId) {
    -    return this.cardOption.swimlaneId == swimlaneId;
    +    let ret = this.cardOption.swimlaneId == swimlaneId;
    +    return ret;
       }
     
    -  /** returns if the list id was the last confirmed one */
    +  /** returns if the list id was the last confirmed one
    +   * @param listId check this list id
    +   * @return if the list id was the last confirmed one
    +   */
       isDialogOptionListId(listId) {
    -    return this.cardOption.listId == listId;
    +    let ret = this.cardOption.listId == listId;
    +    return ret;
       }
     
    -  /** returns all available boards */
    +  /** returns all available board */
       boards() {
    -    return ReactiveCache.getBoards(
    +    const ret = ReactiveCache.getBoards(
           {
             archived: false,
             'members.userId': Meteor.userId(),
    @@ -160,46 +117,26 @@ export class BoardSwimlaneListDialog {
             sort: { sort: 1 },
           },
         );
    +    return ret;
       }
     
       /** returns all available swimlanes of the current board */
       swimlanes() {
         const board = ReactiveCache.getBoard(this.selectedBoardId.get());
    -    return board.swimlanes();
    +    const ret = board.swimlanes();
    +    return ret;
       }
     
       /** returns all available lists of the current board */
       lists() {
    -    return this.getListsForBoardSwimlane(
    -      this.selectedBoardId.get(),
    -      this.selectedSwimlaneId.get(),
    -    );
    +    const board = ReactiveCache.getBoard(this.selectedBoardId.get());
    +    const ret = board.lists();
    +    return ret;
       }
     
    -  /** Fix swimlane title translation issue for "Default" swimlane */
    -  isTitleDefault(title) {
    -    if (
    -      title.startsWith("key 'default") &&
    -      title.endsWith('returned an object instead of string.')
    -    ) {
    -      if (
    -        `${TAPi18n.__('defaultdefault')}`.startsWith("key 'default") &&
    -        `${TAPi18n.__('defaultdefault')}`.endsWith(
    -          'returned an object instead of string.',
    -        )
    -      ) {
    -        return 'Default';
    -      } else {
    -        return `${TAPi18n.__('defaultdefault')}`;
    -      }
    -    } else if (title === 'Default') {
    -      return `${TAPi18n.__('defaultdefault')}`;
    -    } else {
    -      return title;
    -    }
    -  }
    -
    -  /** get the board data from the server */
    +  /** get the board data from the server
    +   * @param boardId get the board data of this board id
    +   */
       getBoardData(boardId) {
         const self = this;
         Meteor.subscribe('board', boardId, false, {
    @@ -208,11 +145,46 @@ export class BoardSwimlaneListDialog {
             self.selectedBoardId.set(boardId);
     
             if (!sameBoardId) {
    +          // reset swimlane id (for selection in cards())
               self.setFirstSwimlaneId();
    +
    +          // reset list id (for selection in cards())
               self.setFirstListId();
             }
           },
         });
       }
     
    +  events() {
    +    return [
    +      {
    +        'click .js-done'() {
    +          const boardSelect = this.$('.js-select-boards')[0];
    +          const boardId = boardSelect.options[boardSelect.selectedIndex].value;
    +
    +          const listSelect = this.$('.js-select-lists')[0];
    +          const listId = listSelect.options[listSelect.selectedIndex].value;
    +
    +          const swimlaneSelect = this.$('.js-select-swimlanes')[0];
    +          const swimlaneId = swimlaneSelect.options[swimlaneSelect.selectedIndex].value;
    +
    +          const options = {
    +            'boardId' : boardId,
    +            'swimlaneId' : swimlaneId,
    +            'listId' : listId,
    +          }
    +          this.setDone(boardId, swimlaneId, listId, options);
    +          Popup.back(2);
    +        },
    +        'change .js-select-boards'(event) {
    +          const boardId = $(event.currentTarget).val();
    +          this.getBoardData(boardId);
    +        },
    +        'change .js-select-swimlanes'(event) {
    +          this.selectedSwimlaneId.set($(event.currentTarget).val());
    +        },
    +      },
    +    ];
    +  }
     }
    +
    diff --git a/client/lib/dialogWithBoardSwimlaneListCard.js b/client/lib/dialogWithBoardSwimlaneListCard.js
    index 1239ce147..a516fde72 100644
    --- a/client/lib/dialogWithBoardSwimlaneListCard.js
    +++ b/client/lib/dialogWithBoardSwimlaneListCard.js
    @@ -1,67 +1,72 @@
     import { ReactiveCache } from '/imports/reactiveCache';
    -import { BoardSwimlaneListDialog } from '/client/lib/dialogWithBoardSwimlaneList';
    +import { DialogWithBoardSwimlaneList } from '/client/lib/dialogWithBoardSwimlaneList';
     
    -/**
    - * Extension of BoardSwimlaneListDialog that adds card selection.
    - * Used by popup templates that need board + swimlane + list + card selectors.
    - */
    -export class BoardSwimlaneListCardDialog extends BoardSwimlaneListDialog {
    -  constructor(tpl, callbacks = {}) {
    -    super(tpl, callbacks);
    -    this.selectedCardId = new ReactiveVar('');
    -  }
    -
    -  getDefaultOption() {
    -    return {
    -      boardId: '',
    -      swimlaneId: '',
    -      listId: '',
    -      cardId: '',
    -    };
    -  }
    -
    -  /** Override to also set cardId if available */
    -  setOption(boardId) {
    -    super.setOption(boardId);
    -    if (this.cardOption && this.cardOption.cardId) {
    -      this.selectedCardId.set(this.cardOption.cardId);
    +export class DialogWithBoardSwimlaneListCard extends DialogWithBoardSwimlaneList {
    +  getDefaultOption(boardId) {
    +    const ret = {
    +      'boardId' : "",
    +      'swimlaneId' : "",
    +      'listId' : "",
    +      'cardId': "",
         }
    +    return ret;
    +  }
    +
    +  /** returns if the card id was the last confirmed one
    +   * @param cardId check this card id
    +   * @return if the card id was the last confirmed one
    +   */
    +  isDialogOptionCardId(cardId) {
    +    let ret = this.cardOption.cardId == cardId;
    +    return ret;
       }
     
       /** returns all available cards of the current list */
       cards() {
    -    const list = ReactiveCache.getList({
    -      _id: this.selectedListId.get(),
    -      boardId: this.selectedBoardId.get(),
    -    });
    -    const swimlaneId = this.selectedSwimlaneId.get();
    -    if (list && swimlaneId) {
    -      return list.cards(swimlaneId).sort((a, b) => a.sort - b.sort);
    -    } else {
    -      return [];
    +    const list = ReactiveCache.getList(this.selectedListId.get());
    +    let ret = [];
    +    if (list) {
    +      ret = list.cards(this.selectedSwimlaneId.get());
         }
    +    return ret;
       }
     
    -  /** returns if the card id was the last confirmed one */
    -  isDialogOptionCardId(cardId) {
    -    return this.cardOption.cardId == cardId;
    -  }
    +  events() {
    +    return [
    +      {
    +        'click .js-done'() {
    +          const boardSelect = this.$('.js-select-boards')[0];
    +          const boardId = boardSelect.options[boardSelect.selectedIndex].value;
     
    -  /** Override to also reset card id on board change */
    -  getBoardData(boardId) {
    -    const self = this;
    -    Meteor.subscribe('board', boardId, false, {
    -      onReady() {
    -        const sameBoardId = self.selectedBoardId.get() == boardId;
    -        self.selectedBoardId.set(boardId);
    +          const listSelect = this.$('.js-select-lists')[0];
    +          const listId = listSelect.options[listSelect.selectedIndex].value;
     
    -        if (!sameBoardId) {
    -          self.setFirstSwimlaneId();
    -          self.setFirstListId();
    -          self.selectedCardId.set('');
    -        }
    +          const swimlaneSelect = this.$('.js-select-swimlanes')[0];
    +          const swimlaneId = swimlaneSelect.options[swimlaneSelect.selectedIndex].value;
    +
    +          const cardSelect = this.$('.js-select-cards')[0];
    +          const cardId = cardSelect.options[cardSelect.selectedIndex].value;
    +
    +          const options = {
    +            'boardId' : boardId,
    +            'swimlaneId' : swimlaneId,
    +            'listId' : listId,
    +            'cardId': cardId,
    +          }
    +          this.setDone(cardId, options);
    +          Popup.back(2);
    +        },
    +        'change .js-select-boards'(event) {
    +          const boardId = $(event.currentTarget).val();
    +          this.getBoardData(boardId);
    +        },
    +        'change .js-select-swimlanes'(event) {
    +          this.selectedSwimlaneId.set($(event.currentTarget).val());
    +        },
    +        'change .js-select-lists'(event) {
    +          this.selectedListId.set($(event.currentTarget).val());
    +        },
           },
    -    });
    +    ];
       }
    -
     }
    diff --git a/client/lib/escapeActions.js b/client/lib/escapeActions.js
    index e76221074..986611326 100644
    --- a/client/lib/escapeActions.js
    +++ b/client/lib/escapeActions.js
    @@ -1,6 +1,4 @@
    -const hotkeys = require('hotkeys-js').default;
    -
    -// Pressing `Escape` should close the last opened "element" and only the last
    +// Pressing `Escape` should close the last opened “element” and only the last
     // one. Components can register themselves using a label a condition, and an
     // action. This is used by Popup or inlinedForm for instance. When we press
     // escape we execute the action which have a valid condition and his the highest
    @@ -121,9 +119,9 @@ EscapeActions = {
       },
     };
     
    -// Pressing escape to execute one escape action. ESC is allowed globally
    -// in the hotkeys filter (keyboard.js) so it works in textarea and inputs.
    -hotkeys('escape', () => {
    +// Pressing escape to execute one escape action. We use `bindGloabal` vecause
    +// the shortcut sould work on textarea and inputs as well.
    +Mousetrap.bindGlobal('esc', () => {
       EscapeActions.executeLowest();
       Sidebar.hide();
     });
    diff --git a/client/lib/infiniteScrolling.js b/client/lib/infiniteScrolling.js
    deleted file mode 100644
    index fd0aa1d39..000000000
    --- a/client/lib/infiniteScrolling.js
    +++ /dev/null
    @@ -1,47 +0,0 @@
    -const PEAK_ANTICIPATION = 200;
    -
    -/**
    - * Infinite scrolling utility to replace the BlazeComponent mixin.
    - *
    - * Usage in a Template:
    - *   Template.myTemplate.onCreated(function () {
    - *     this.infiniteScrolling = new InfiniteScrolling();
    - *   });
    - *
    - * The scroll event must be wired in Template.events:
    - *   'scroll .my-container'(event, tpl) {
    - *     tpl.infiniteScrolling.checkScrollPosition(event.currentTarget, () => {
    - *       tpl.loadNextPage();
    - *     });
    - *   },
    - *
    - * Or for components that delegate to a child for loading:
    - *   tpl.infiniteScrolling.checkScrollPosition(event.currentTarget, () => {
    - *     activitiesComponent.loadNextPage();
    - *   });
    - */
    -export class InfiniteScrolling {
    -  constructor() {
    -    this._nextPeak = Infinity;
    -  }
    -
    -  setNextPeak(v) {
    -    this._nextPeak = v;
    -  }
    -
    -  getNextPeak() {
    -    return this._nextPeak;
    -  }
    -
    -  resetNextPeak() {
    -    this._nextPeak = Infinity;
    -  }
    -
    -  checkScrollPosition(domElement, reachNextPeakCallback) {
    -    let altitude = domElement.scrollTop + domElement.offsetHeight;
    -    altitude += PEAK_ANTICIPATION;
    -    if (altitude >= this._nextPeak) {
    -      reachNextPeakCallback();
    -    }
    -  }
    -}
    diff --git a/client/lib/inlinedform.js b/client/lib/inlinedform.js
    index d4da6c647..409b259d0 100644
    --- a/client/lib/inlinedform.js
    +++ b/client/lib/inlinedform.js
    @@ -15,77 +15,75 @@
     // We can only have one inlined form element opened at a time
     const currentlyOpenedForm = new ReactiveVar(null);
     
    -Template.inlinedForm.onCreated(function () {
    -  this.isOpen = new ReactiveVar(false);
    -});
    -
    -Template.inlinedForm.onRendered(function () {
    -  const tpl = this;
    -  tpl.autorun(() => {
    -    if (tpl.isOpen.get()) {
    -      Tracker.afterFlush(() => {
    -        const input = tpl.find('textarea,input[type=text]');
    -        if (input && typeof input.focus === 'function') {
    -          setTimeout(() => {
    -            input.focus();
    -            if (input.value && input.select) {
    -              input.select();
    -            }
    -          }, 50);
    -        }
    -      });
    -    }
    -  });
    -});
    -
    -Template.inlinedForm.onDestroyed(function () {
    -  currentlyOpenedForm.set(null);
    -});
    -
    -Template.inlinedForm.helpers({
    -  isOpen() {
    -    return Template.instance().isOpen;
    +InlinedForm = BlazeComponent.extendComponent({
    +  template() {
    +    return 'inlinedForm';
       },
    -});
     
    -Template.inlinedForm.events({
    -  'click .js-close-inlined-form'(evt, tpl) {
    -    tpl.isOpen.set(false);
    +  onCreated() {
    +    this.isOpen = new ReactiveVar(false);
    +  },
    +
    +  onDestroyed() {
         currentlyOpenedForm.set(null);
       },
    -  'click .js-open-inlined-form'(evt, tpl) {
    -    evt.preventDefault();
    -    EscapeActions.clickExecute(evt.target, 'inlinedForm');
    -    tpl.isOpen.set(true);
    -    currentlyOpenedForm.set(tpl);
    -  },
    -  'keydown form textarea'(evt, tpl) {
    -    if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) {
    -      tpl.find('button[type=submit]').click();
    +
    +  open(evt) {
    +    if (evt) {
    +      evt.preventDefault();
    +      // Close currently opened form, if any
    +      EscapeActions.clickExecute(evt.target, 'inlinedForm');
    +    } else {
    +      // Close currently opened form, if any
    +      EscapeActions.executeUpTo('inlinedForm');
         }
    +
    +    this.isOpen.set(true);
    +    currentlyOpenedForm.set(this);
       },
    -  submit(evt, tpl) {
    -    const data = Template.currentData();
    -    if (data.autoclose !== false) {
    -      Tracker.afterFlush(() => {
    -        tpl.isOpen.set(false);
    -        currentlyOpenedForm.set(null);
    -      });
    -    }
    +
    +  close() {
    +    this.isOpen.set(false);
    +    currentlyOpenedForm.set(null);
       },
    -});
    +
    +  getValue() {
    +    const input = this.find('textarea,input[type=text]');
    +    // \s without \n + unicode (https://developer.mozilla.org/de/docs/Web/JavaScript/Guide/Regular_Expressions#special-white-space)
    +    return this.isOpen.get() && input && input.value.replaceAll(/[ \f\r\t\v]+$/gm, '');
    +  },
    +
    +  events() {
    +    return [
    +      {
    +        'click .js-close-inlined-form': this.close,
    +        'click .js-open-inlined-form': this.open,
    +
    +        // Pressing Ctrl+Enter should submit the form
    +        'keydown form textarea'(evt) {
    +          if (evt.keyCode === 13 && (evt.metaKey || evt.ctrlKey)) {
    +            this.find('button[type=submit]').click();
    +          }
    +        },
    +
    +        // Close the inlined form when after its submission
    +        submit() {
    +          if (this.currentData().autoclose !== false) {
    +            Tracker.afterFlush(() => {
    +              this.close();
    +            });
    +          }
    +        },
    +      },
    +    ];
    +  },
    +}).register('inlinedForm');
     
     // Press escape to close the currently opened inlinedForm
     EscapeActions.register(
       'inlinedForm',
       () => {
    -    const form = currentlyOpenedForm.get();
    -    if (form) {
    -      if (form.isOpen) {
    -        form.isOpen.set(false);
    -        currentlyOpenedForm.set(null);
    -      }
    -    }
    +    currentlyOpenedForm.get().close();
       },
       () => {
         return currentlyOpenedForm.get() !== null;
    @@ -94,3 +92,13 @@ EscapeActions.register(
         enabledOnClick: false,
       },
     );
    +
    +// submit on click outside
    +//document.addEventListener('click', function(evt) {
    +//  const openedForm = currentlyOpenedForm.get();
    +//  const isClickOutside = $(evt.target).closest('.js-inlined-form').length === 0;
    +//  if (openedForm && isClickOutside) {
    +//    $('.js-inlined-form button[type=submit]').click();
    +//    openedForm.close();
    +//  }
    +//}, true);
    diff --git a/client/lib/jquery-ui.js b/client/lib/jquery-ui.js
    index dc7a428cb..841e3c986 100644
    --- a/client/lib/jquery-ui.js
    +++ b/client/lib/jquery-ui.js
    @@ -4,10 +4,13 @@ require('jquery-ui/ui/widget')
     require('jquery-ui/ui/scroll-parent')
     require('jquery-ui/ui/data')
     require('jquery-ui/ui/widgets/mouse')
    +require('jquery-ui/ui/ie')
     require('jquery-ui/ui/widgets/sortable')
     
     // required for draggable
     require('jquery-ui/ui/plugin')
    +require('jquery-ui/ui/safe-active-element')
    +require('jquery-ui/ui/safe-blur')
     require('jquery-ui/ui/widgets/draggable')
     
     // everything already required for droppable
    diff --git a/client/lib/keyboard.js b/client/lib/keyboard.js
    index 7a72df472..f817e9aa3 100644
    --- a/client/lib/keyboard.js
    +++ b/client/lib/keyboard.js
    @@ -1,42 +1,8 @@
     import { ReactiveCache } from '/imports/reactiveCache';
    -import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
    -const hotkeys = require('hotkeys-js').default;
     
     // XXX There is no reason to define these shortcuts globally, they should be
     // attached to a template (most of them will go in the `board` template).
     
    -// Configure hotkeys filter (replaces Mousetrap.stopCallback)
    -// CRITICAL: Return values are INVERTED from Mousetrap's stopCallback
    -// hotkeys filter: true = ALLOW shortcut, false = STOP shortcut
    -hotkeys.filter = (event) => {
    -  // Are shortcuts enabled for the user?
    -  if (ReactiveCache.getCurrentUser() && !ReactiveCache.getCurrentUser().isKeyboardShortcuts())
    -    return false;
    -
    -  // Always handle escape
    -  if (event.keyCode === 27)
    -    return true;
    -
    -  // Make sure there are no selected characters
    -  if (window.getSelection().type === "Range")
    -    return false;
    -
    -  // Decide what the current element is
    -  const currentElement = event.target || document.activeElement;
    -
    -  // If the current element is editable, we don't want to trigger an event
    -  if (currentElement.isContentEditable)
    -    return false;
    -
    -  // Make sure we are not in an input element
    -  if (currentElement instanceof HTMLInputElement || currentElement instanceof HTMLSelectElement || currentElement instanceof HTMLTextAreaElement)
    -    return false;
    -
    -  // We can trigger events!
    -  return true;
    -};
    -
    -// Handle non-Latin keyboards
     window.addEventListener('keydown', (e) => {
       // Only handle event if coming from body
       if (e.target !== document.body) return;
    @@ -44,19 +10,39 @@ window.addEventListener('keydown', (e) => {
       // Only handle event if it's in another language
       if (String.fromCharCode(e.which).toLowerCase() === e.key) return;
     
    -  // Trigger the corresponding action by dispatching a new event with the ASCII key
    -  const key = String.fromCharCode(e.which).toLowerCase();
    -  // Create a synthetic event for hotkeys to handle
    -  const syntheticEvent = new KeyboardEvent('keydown', {
    -    key: key,
    -    keyCode: e.which,
    -    which: e.which,
    -    bubbles: true,
    -    cancelable: true,
    -  });
    -  document.dispatchEvent(syntheticEvent);
    +  // Trigger the corresponding action
    +  Mousetrap.handleKey(String.fromCharCode(e.which).toLowerCase(), [], {type: "keypress"});
     });
     
    +// Overwrite the stopCallback to allow for more keyboard shortcut customizations
    +Mousetrap.stopCallback = (event, element) => {
    +  // Are shortcuts enabled for the user?
    +  if (ReactiveCache.getCurrentUser() && !ReactiveCache.getCurrentUser().isKeyboardShortcuts())
    +    return true;
    +
    +  // Always handle escape
    +  if (event.keyCode === 27)
    +    return false;
    +
    +  // Make sure there are no selected characters
    +  if (window.getSelection().type === "Range")
    +    return true;
    +
    +  // Decide what the current element is
    +  const currentElement = event.target || document.activeElement;
    +
    +  // If the current element is editable, we don't want to trigger an event
    +  if (currentElement.isContentEditable)
    +    return true;
    +
    +  // Make sure we are not in an input element
    +  if (currentElement instanceof HTMLInputElement || currentElement instanceof HTMLSelectElement || currentElement instanceof HTMLTextAreaElement)
    +    return true;
    +
    +  // We can trigger events!
    +  return false;
    +}
    +
     function getHoveredCardId() {
       const card = $('.js-minicard:hover').get(0);
       if (!card) return null;
    @@ -67,13 +53,11 @@ function getSelectedCardId() {
       return Session.get('currentCard') || Session.get('selectedCard') || getHoveredCardId();
     }
     
    -hotkeys('?', (event) => {
    -  event.preventDefault();
    +Mousetrap.bind('?', () => {
       FlowRouter.go('shortcuts');
     });
     
    -hotkeys('w', (event) => {
    -  event.preventDefault();
    +Mousetrap.bind('w', () => {
       if (Sidebar.isOpen() && Sidebar.getView() === 'home') {
         Sidebar.toggle();
       } else {
    @@ -81,8 +65,7 @@ hotkeys('w', (event) => {
       }
     });
     
    -hotkeys('q', (event) => {
    -  event.preventDefault();
    +Mousetrap.bind('q', () => {
       const currentBoardId = Session.get('currentBoard');
       const currentUserId = Meteor.userId();
       if (currentBoardId && currentUserId) {
    @@ -90,8 +73,7 @@ hotkeys('q', (event) => {
       }
     });
     
    -hotkeys('a', (event) => {
    -  event.preventDefault();
    +Mousetrap.bind('a', () => {
       const currentBoardId = Session.get('currentBoard');
       const currentUserId = Meteor.userId();
       if (currentBoardId && currentUserId) {
    @@ -99,15 +81,13 @@ hotkeys('a', (event) => {
       }
     });
     
    -hotkeys('x', (event) => {
    -  event.preventDefault();
    +Mousetrap.bind('x', () => {
       if (Filter.isActive()) {
         Filter.reset();
       }
     });
     
    -hotkeys('f', (event) => {
    -  event.preventDefault();
    +Mousetrap.bind('f', () => {
       if (Sidebar.isOpen() && Sidebar.getView() === 'filter') {
         Sidebar.toggle();
       } else {
    @@ -115,8 +95,7 @@ hotkeys('f', (event) => {
       }
     });
     
    -hotkeys('/', (event) => {
    -  event.preventDefault();
    +Mousetrap.bind('/', () => {
       if (Sidebar.isOpen() && Sidebar.getView() === 'search') {
         Sidebar.toggle();
       } else {
    @@ -124,13 +103,12 @@ hotkeys('/', (event) => {
       }
     });
     
    -hotkeys('down,up', (event, handler) => {
    -  event.preventDefault();
    +Mousetrap.bind(['down', 'up'], (evt, key) => {
       if (!Utils.getCurrentCardId()) {
         return;
       }
     
    -  const nextFunc = handler.key === 'down' ? 'next' : 'prev';
    +  const nextFunc = key === 'down' ? 'next' : 'prev';
       const nextCard = $('.js-minicard.is-selected')
         [nextFunc]('.js-minicard')
         .get(0);
    @@ -140,47 +118,49 @@ hotkeys('down,up', (event, handler) => {
       }
     });
     
    -// Shift + number keys to remove labels in multiselect
    -const shiftNums = _.range(1, 10).map(x => `shift+${x}`).join(',');
    -hotkeys(shiftNums, (event, handler) => {
    -  event.preventDefault();
    -  const num = parseInt(handler.key.split('+')[1]);
    +numbArray = _.range(1,10).map(x => 'shift+'+String(x))
    +Mousetrap.bind(numbArray, (evt, key) => {
    +  num = parseInt(key.substr(6, key.length));
       const currentUserId = Meteor.userId();
       if (currentUserId === null) {
         return;
       }
       const currentBoardId = Session.get('currentBoard');
    -  const board = ReactiveCache.getBoard(currentBoardId);
    -  const labels = board.labels;
    -  if (MultiSelection.isActive()) {
    +  board = ReactiveCache.getBoard(currentBoardId);
    +  labels = board.labels;
    +  if(MultiSelection.isActive())
    +  {
         const cardIds = MultiSelection.getSelectedCardIds();
    -    for (const cardId of cardIds) {
    -      const card = Cards.findOne(cardId);
    -      if (num <= board.labels.length) {
    -        card.removeLabel(labels[num - 1]["_id"]);
    +    for (const cardId of cardIds)
    +    {
    +      card = Cards.findOne(cardId);
    +      if(num <= board.labels.length)
    +      {
    +        card.removeLabel(labels[num-1]["_id"]);
           }
         }
       }
     });
     
    -// Number keys to toggle labels
    -const nums = _.range(1, 10).join(',');
    -hotkeys(nums, (event, handler) => {
    -  event.preventDefault();
    -  const num = parseInt(handler.key);
    +numArray = _.range(1,10).map(x => String(x))
    +Mousetrap.bind(numArray, (evt, key) => {
    +  num = parseInt(key);
       const currentUserId = Meteor.userId();
       const currentBoardId = Session.get('currentBoard');
       if (currentUserId === null) {
         return;
       }
    -  const board = ReactiveCache.getBoard(currentBoardId);
    -  const labels = board.labels;
    -  if (MultiSelection.isActive() && ReactiveCache.getCurrentUser().isBoardMember()) {
    +  board = ReactiveCache.getBoard(currentBoardId);
    +  labels = board.labels;
    +  if(MultiSelection.isActive() && ReactiveCache.getCurrentUser().isBoardMember())
    +  {
         const cardIds = MultiSelection.getSelectedCardIds();
    -    for (const cardId of cardIds) {
    -      const card = Cards.findOne(cardId);
    -      if (num <= board.labels.length) {
    -        card.addLabel(labels[num - 1]["_id"]);
    +    for (const cardId of cardIds)
    +    {
    +      card = Cards.findOne(cardId);
    +      if(num <= board.labels.length)
    +      {
    +        card.addLabel(labels[num-1]["_id"]);
           }
         }
         return;
    @@ -192,16 +172,14 @@ hotkeys(nums, (event, handler) => {
       }
       if (ReactiveCache.getCurrentUser().isBoardMember()) {
         const card = Cards.findOne(cardId);
    -    if (num <= board.labels.length) {
    -      card.toggleLabel(labels[num - 1]["_id"]);
    +    if(num <= board.labels.length)
    +    {
    +      card.toggleLabel(labels[num-1]["_id"]);
         }
       }
     });
     
    -// Ctrl+Alt + number keys to toggle assignees
    -const ctrlAltNums = _.range(1, 10).map(x => `ctrl+alt+${x}`).join(',');
    -hotkeys(ctrlAltNums, (event, handler) => {
    -  event.preventDefault();
    +Mousetrap.bind(_.range(1, 10).map(x => `ctrl+alt+${x}`), (evt, key) => {
       // Make sure the current user is defined
       if (!ReactiveCache.getCurrentUser())
         return;
    @@ -210,7 +188,7 @@ hotkeys(ctrlAltNums, (event, handler) => {
       if (!ReactiveCache.getCurrentUser().isBoardMember())
         return;
     
    -  const memberIndex = parseInt(handler.key.split("+").pop()) - 1;
    +  const memberIndex = parseInt(key.split("+").pop()) - 1;
       const currentBoard = Utils.getCurrentBoard();
       const validBoardMembers = currentBoard.memberUsers().filter(member => member.isBoardMember());
     
    @@ -232,8 +210,7 @@ hotkeys(ctrlAltNums, (event, handler) => {
       }
     });
     
    -hotkeys('m', (event) => {
    -  event.preventDefault();
    +Mousetrap.bind('m', evt => {
       const cardId = getSelectedCardId();
       if (!cardId) {
         return;
    @@ -247,11 +224,13 @@ hotkeys('m', (event) => {
       if (ReactiveCache.getCurrentUser().isBoardMember()) {
         const card = Cards.findOne(cardId);
         card.toggleAssignee(currentUserId);
    +    // We should prevent scrolling in card when spacebar is clicked
    +    // This should do it according to Mousetrap docs, but it doesn't
    +    evt.preventDefault();
       }
     });
     
    -hotkeys('space', (event) => {
    -  event.preventDefault();
    +Mousetrap.bind('space', evt => {
       const cardId = getSelectedCardId();
       if (!cardId) {
         return;
    @@ -265,11 +244,13 @@ hotkeys('space', (event) => {
       if (ReactiveCache.getCurrentUser().isBoardMember()) {
         const card = Cards.findOne(cardId);
         card.toggleMember(currentUserId);
    +    // We should prevent scrolling in card when spacebar is clicked
    +    // This should do it according to Mousetrap docs, but it doesn't
    +    evt.preventDefault();
       }
     });
     
    -const archiveCard = async (event) => {
    -  event.preventDefault();
    +const archiveCard = evt => {
       const cardId = getSelectedCardId();
       if (!cardId) {
         return;
    @@ -282,20 +263,22 @@ const archiveCard = async (event) => {
     
       if (Utils.canModifyBoard()) {
         const card = Cards.findOne(cardId);
    -    await card.archive();
    +    card.archive();
    +    // We should prevent scrolling in card when spacebar is clicked
    +    // This should do it according to Mousetrap docs, but it doesn't
    +    evt.preventDefault();
       }
     };
     
     // Archive card has multiple shortcuts
    -hotkeys('c', archiveCard);
    -hotkeys('-', archiveCard);
    +Mousetrap.bind('c', archiveCard);
    +Mousetrap.bind('-', archiveCard);
     
     // Same as above, this time for Persian keyboard.
     // https://github.com/wekan/wekan/pull/5589#issuecomment-2516776519
    -hotkeys('\xf7', archiveCard);
    +Mousetrap.bind('÷', archiveCard);
     
    -hotkeys('n', (event) => {
    -  event.preventDefault();
    +Mousetrap.bind('n', evt => {
       const cardId = getSelectedCardId();
       if (!cardId) {
         return;
    @@ -312,6 +295,10 @@ hotkeys('n', (event) => {
     
         // Find the button and click it
         $(`#js-list-${card.listId} .list-body .minicards .open-minicard-composer`).click();
    +
    +    // We should prevent scrolling in card when spacebar is clicked
    +    // This should do it according to Mousetrap docs, but it doesn't
    +    evt.preventDefault();
       }
     });
     
    @@ -366,7 +353,7 @@ Template.keyboardShortcuts.helpers({
           action: 'shortcut-assign-self',
         },
         {
    -      keys: ['c', '\xf7', '-'],
    +      keys: ['c', '÷', '-'],
           action: 'archive-card',
         },
         {
    diff --git a/client/lib/localStorageValidator.js b/client/lib/localStorageValidator.js
    deleted file mode 100644
    index f03988a18..000000000
    --- a/client/lib/localStorageValidator.js
    +++ /dev/null
    @@ -1,278 +0,0 @@
    -/**
    - * LocalStorage Validation and Cleanup Utility
    - * 
    - * Validates and cleans up per-user UI state stored in localStorage
    - * for non-logged-in users (swimlane heights, list widths, collapse states)
    - */
    -
    -// Maximum age for localStorage data (90 days)
    -const MAX_AGE_MS = 90 * 24 * 60 * 60 * 1000;
    -
    -// Maximum number of boards to keep per storage key
    -const MAX_BOARDS_PER_KEY = 50;
    -
    -// Maximum number of items per board
    -const MAX_ITEMS_PER_BOARD = 100;
    -
    -/**
    - * Validate that a value is a valid positive number
    - */
    -function isValidNumber(value, min = 0, max = 10000) {
    -  if (typeof value !== 'number') return false;
    -  if (isNaN(value)) return false;
    -  if (!isFinite(value)) return false;
    -  if (value < min || value > max) return false;
    -  return true;
    -}
    -
    -/**
    - * Validate that a value is a valid boolean
    - */
    -function isValidBoolean(value) {
    -  return typeof value === 'boolean';
    -}
    -
    -/**
    - * Validate and clean swimlane heights data
    - * Structure: { boardId: { swimlaneId: height, ... }, ... }
    - */
    -function validateSwimlaneHeights(data) {
    -  if (!data || typeof data !== 'object') return {};
    -  
    -  const cleaned = {};
    -  const boardIds = Object.keys(data).slice(0, MAX_BOARDS_PER_KEY);
    -  
    -  for (const boardId of boardIds) {
    -    if (typeof boardId !== 'string' || boardId.length === 0) continue;
    -    
    -    const boardData = data[boardId];
    -    if (!boardData || typeof boardData !== 'object') continue;
    -    
    -    const swimlaneIds = Object.keys(boardData).slice(0, MAX_ITEMS_PER_BOARD);
    -    const cleanedBoard = {};
    -    
    -    for (const swimlaneId of swimlaneIds) {
    -      if (typeof swimlaneId !== 'string' || swimlaneId.length === 0) continue;
    -      
    -      const height = boardData[swimlaneId];
    -      // Valid swimlane heights: -1 (auto) or 50-2000 pixels
    -      if (isValidNumber(height, -1, 2000)) {
    -        cleanedBoard[swimlaneId] = height;
    -      }
    -    }
    -    
    -    if (Object.keys(cleanedBoard).length > 0) {
    -      cleaned[boardId] = cleanedBoard;
    -    }
    -  }
    -  
    -  return cleaned;
    -}
    -
    -/**
    - * Validate and clean list widths data
    - * Structure: { boardId: { listId: width, ... }, ... }
    - */
    -function validateListWidths(data) {
    -  if (!data || typeof data !== 'object') return {};
    -  
    -  const cleaned = {};
    -  const boardIds = Object.keys(data).slice(0, MAX_BOARDS_PER_KEY);
    -  
    -  for (const boardId of boardIds) {
    -    if (typeof boardId !== 'string' || boardId.length === 0) continue;
    -    
    -    const boardData = data[boardId];
    -    if (!boardData || typeof boardData !== 'object') continue;
    -    
    -    const listIds = Object.keys(boardData).slice(0, MAX_ITEMS_PER_BOARD);
    -    const cleanedBoard = {};
    -    
    -    for (const listId of listIds) {
    -      if (typeof listId !== 'string' || listId.length === 0) continue;
    -      
    -      const width = boardData[listId];
    -      // Valid list widths: 100-1000 pixels
    -      if (isValidNumber(width, 100, 1000)) {
    -        cleanedBoard[listId] = width;
    -      }
    -    }
    -    
    -    if (Object.keys(cleanedBoard).length > 0) {
    -      cleaned[boardId] = cleanedBoard;
    -    }
    -  }
    -  
    -  return cleaned;
    -}
    -
    -/**
    - * Validate and clean collapsed states data
    - * Structure: { boardId: { itemId: boolean, ... }, ... }
    - */
    -function validateCollapsedStates(data) {
    -  if (!data || typeof data !== 'object') return {};
    -  
    -  const cleaned = {};
    -  const boardIds = Object.keys(data).slice(0, MAX_BOARDS_PER_KEY);
    -  
    -  for (const boardId of boardIds) {
    -    if (typeof boardId !== 'string' || boardId.length === 0) continue;
    -    
    -    const boardData = data[boardId];
    -    if (!boardData || typeof boardData !== 'object') continue;
    -    
    -    const itemIds = Object.keys(boardData).slice(0, MAX_ITEMS_PER_BOARD);
    -    const cleanedBoard = {};
    -    
    -    for (const itemId of itemIds) {
    -      if (typeof itemId !== 'string' || itemId.length === 0) continue;
    -      
    -      const collapsed = boardData[itemId];
    -      if (isValidBoolean(collapsed)) {
    -        cleanedBoard[itemId] = collapsed;
    -      }
    -    }
    -    
    -    if (Object.keys(cleanedBoard).length > 0) {
    -      cleaned[boardId] = cleanedBoard;
    -    }
    -  }
    -  
    -  return cleaned;
    -}
    -
    -/**
    - * Validate and clean a single localStorage key
    - */
    -function validateAndCleanKey(key, validator) {
    -  try {
    -    const stored = localStorage.getItem(key);
    -    if (!stored) return;
    -    
    -    const data = JSON.parse(stored);
    -    const cleaned = validator(data);
    -    
    -    // Only write back if data changed
    -    const cleanedStr = JSON.stringify(cleaned);
    -    if (cleanedStr !== stored) {
    -      if (Object.keys(cleaned).length > 0) {
    -        localStorage.setItem(key, cleanedStr);
    -      } else {
    -        localStorage.removeItem(key);
    -      }
    -    }
    -  } catch (e) {
    -    console.warn(`Error validating localStorage key ${key}:`, e);
    -    // Remove corrupted data
    -    try {
    -      localStorage.removeItem(key);
    -    } catch (removeError) {
    -      console.error(`Failed to remove corrupted localStorage key ${key}:`, removeError);
    -    }
    -  }
    -}
    -
    -/**
    - * Validate and clean all Wekan localStorage data
    - * Called on app startup and periodically
    - */
    -export function validateAndCleanLocalStorage() {
    -  if (typeof localStorage === 'undefined') return;
    -  
    -  try {
    -    // Validate swimlane heights
    -    validateAndCleanKey('wekan-swimlane-heights', validateSwimlaneHeights);
    -    
    -    // Validate list widths
    -    validateAndCleanKey('wekan-list-widths', validateListWidths);
    -    
    -    // Validate list constraints
    -    validateAndCleanKey('wekan-list-constraints', validateListWidths);
    -    
    -    // Validate collapsed lists
    -    validateAndCleanKey('wekan-collapsed-lists', validateCollapsedStates);
    -    
    -    // Validate collapsed swimlanes
    -    validateAndCleanKey('wekan-collapsed-swimlanes', validateCollapsedStates);
    -    
    -    // Record last cleanup time
    -    localStorage.setItem('wekan-last-cleanup', Date.now().toString());
    -    
    -  } catch (e) {
    -    console.error('Error during localStorage validation:', e);
    -  }
    -}
    -
    -/**
    - * Check if cleanup is needed (once per day)
    - */
    -export function shouldRunCleanup() {
    -  if (typeof localStorage === 'undefined') return false;
    -  
    -  try {
    -    const lastCleanup = localStorage.getItem('wekan-last-cleanup');
    -    if (!lastCleanup) return true;
    -    
    -    const lastCleanupTime = parseInt(lastCleanup, 10);
    -    if (isNaN(lastCleanupTime)) return true;
    -    
    -    const timeSince = Date.now() - lastCleanupTime;
    -    // Run cleanup once per day
    -    return timeSince > 24 * 60 * 60 * 1000;
    -  } catch (e) {
    -    return true;
    -  }
    -}
    -
    -/**
    - * Get validated data from localStorage
    - */
    -export function getValidatedLocalStorageData(key, validator) {
    -  if (typeof localStorage === 'undefined') return {};
    -  
    -  try {
    -    const stored = localStorage.getItem(key);
    -    if (!stored) return {};
    -    
    -    const data = JSON.parse(stored);
    -    return validator(data);
    -  } catch (e) {
    -    console.warn(`Error reading localStorage key ${key}:`, e);
    -    return {};
    -  }
    -}
    -
    -/**
    - * Set validated data to localStorage
    - */
    -export function setValidatedLocalStorageData(key, data, validator) {
    -  if (typeof localStorage === 'undefined') return false;
    -  
    -  try {
    -    const validated = validator(data);
    -    localStorage.setItem(key, JSON.stringify(validated));
    -    return true;
    -  } catch (e) {
    -    console.error(`Error writing localStorage key ${key}:`, e);
    -    return false;
    -  }
    -}
    -
    -// Export validators for use by other modules
    -export const validators = {
    -  swimlaneHeights: validateSwimlaneHeights,
    -  listWidths: validateListWidths,
    -  collapsedStates: validateCollapsedStates,
    -  isValidNumber,
    -  isValidBoolean,
    -};
    -
    -// Auto-cleanup on module load if needed
    -if (Meteor.isClient) {
    -  Meteor.startup(() => {
    -    if (shouldRunCleanup()) {
    -      validateAndCleanLocalStorage();
    -    }
    -  });
    -}
    diff --git a/client/lib/migrationManager.js b/client/lib/migrationManager.js
    new file mode 100644
    index 000000000..19ea53f10
    --- /dev/null
    +++ b/client/lib/migrationManager.js
    @@ -0,0 +1,815 @@
    +/**
    + * Migration Manager
    + * Handles all database migrations as steps during board loading
    + * with detailed progress tracking and background persistence
    + */
    +
    +import { ReactiveVar } from 'meteor/reactive-var';
    +import { ReactiveCache } from '/imports/reactiveCache';
    +
    +// Reactive variables for migration progress
    +export const migrationProgress = new ReactiveVar(0);
    +export const migrationStatus = new ReactiveVar('');
    +export const migrationCurrentStep = new ReactiveVar('');
    +export const migrationSteps = new ReactiveVar([]);
    +export const isMigrating = new ReactiveVar(false);
    +export const migrationEstimatedTime = new ReactiveVar('');
    +
    +class MigrationManager {
    +  constructor() {
    +    this.migrationCache = new Map(); // Cache completed migrations
    +    this.steps = this.initializeMigrationSteps();
    +    this.currentStepIndex = 0;
    +    this.startTime = null;
    +  }
    +
    +  /**
    +   * Initialize all migration steps with their details
    +   */
    +  initializeMigrationSteps() {
    +    return [
    +      {
    +        id: 'board-background-color',
    +        name: 'Board Background Colors',
    +        description: 'Setting up board background colors',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-cardcounterlist-allowed',
    +        name: 'Card Counter List Settings',
    +        description: 'Adding card counter list permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-boardmemberlist-allowed',
    +        name: 'Board Member List Settings',
    +        description: 'Adding board member list permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'lowercase-board-permission',
    +        name: 'Board Permission Standardization',
    +        description: 'Converting board permissions to lowercase',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'change-attachments-type-for-non-images',
    +        name: 'Attachment Type Standardization',
    +        description: 'Updating attachment types for non-images',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'card-covers',
    +        name: 'Card Covers System',
    +        description: 'Setting up card cover functionality',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'use-css-class-for-boards-colors',
    +        name: 'Board Color CSS Classes',
    +        description: 'Converting board colors to CSS classes',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'denormalize-star-number-per-board',
    +        name: 'Board Star Counts',
    +        description: 'Calculating star counts per board',
    +        weight: 3,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-member-isactive-field',
    +        name: 'Member Activity Status',
    +        description: 'Adding member activity tracking',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-sort-checklists',
    +        name: 'Checklist Sorting',
    +        description: 'Adding sort order to checklists',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-swimlanes',
    +        name: 'Swimlanes System',
    +        description: 'Setting up swimlanes functionality',
    +        weight: 4,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-views',
    +        name: 'Board Views',
    +        description: 'Adding board view options',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-checklist-items',
    +        name: 'Checklist Items',
    +        description: 'Setting up checklist items system',
    +        weight: 3,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-card-types',
    +        name: 'Card Types',
    +        description: 'Adding card type functionality',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-custom-fields-to-cards',
    +        name: 'Custom Fields',
    +        description: 'Adding custom fields to cards',
    +        weight: 3,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-requester-field',
    +        name: 'Requester Field',
    +        description: 'Adding requester field to cards',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-assigner-field',
    +        name: 'Assigner Field',
    +        description: 'Adding assigner field to cards',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-parent-field-to-cards',
    +        name: 'Card Parent Relationships',
    +        description: 'Adding parent field to cards',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-subtasks-boards',
    +        name: 'Subtasks Boards',
    +        description: 'Setting up subtasks board functionality',
    +        weight: 3,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-subtasks-sort',
    +        name: 'Subtasks Sorting',
    +        description: 'Adding sort order to subtasks',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-subtasks-allowed',
    +        name: 'Subtasks Permissions',
    +        description: 'Adding subtasks permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-authenticationMethod',
    +        name: 'Authentication Methods',
    +        description: 'Adding authentication method tracking',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'remove-tag',
    +        name: 'Remove Tag Field',
    +        description: 'Removing deprecated tag field',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'remove-customFields-references-broken',
    +        name: 'Fix Custom Fields References',
    +        description: 'Fixing broken custom field references',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-product-name',
    +        name: 'Product Name Settings',
    +        description: 'Adding product name configuration',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-hide-logo',
    +        name: 'Hide Logo Setting',
    +        description: 'Adding hide logo option',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-hide-card-counter-list',
    +        name: 'Hide Card Counter Setting',
    +        description: 'Adding hide card counter option',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-hide-board-member-list',
    +        name: 'Hide Board Member List Setting',
    +        description: 'Adding hide board member list option',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-displayAuthenticationMethod',
    +        name: 'Display Authentication Method',
    +        description: 'Adding authentication method display option',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-defaultAuthenticationMethod',
    +        name: 'Default Authentication Method',
    +        description: 'Setting default authentication method',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-templates',
    +        name: 'Board Templates',
    +        description: 'Setting up board templates system',
    +        weight: 3,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'fix-circular-reference_',
    +        name: 'Fix Circular References',
    +        description: 'Fixing circular references in cards',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'mutate-boardIds-in-customfields',
    +        name: 'Custom Fields Board IDs',
    +        description: 'Updating board IDs in custom fields',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-missing-created-and-modified',
    +        name: 'Missing Timestamps',
    +        description: 'Adding missing created and modified timestamps',
    +        weight: 4,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'fix-incorrect-dates',
    +        name: 'Fix Incorrect Dates',
    +        description: 'Correcting incorrect date values',
    +        weight: 3,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-assignee',
    +        name: 'Assignee Field',
    +        description: 'Adding assignee field to cards',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-profile-showDesktopDragHandles',
    +        name: 'Desktop Drag Handles',
    +        description: 'Adding desktop drag handles preference',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-profile-hiddenMinicardLabelText',
    +        name: 'Hidden Minicard Labels',
    +        description: 'Adding hidden minicard label text preference',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-receiveddate-allowed',
    +        name: 'Received Date Permissions',
    +        description: 'Adding received date permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-startdate-allowed',
    +        name: 'Start Date Permissions',
    +        description: 'Adding start date permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-duedate-allowed',
    +        name: 'Due Date Permissions',
    +        description: 'Adding due date permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-enddate-allowed',
    +        name: 'End Date Permissions',
    +        description: 'Adding end date permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-members-allowed',
    +        name: 'Members Permissions',
    +        description: 'Adding members permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-assignee-allowed',
    +        name: 'Assignee Permissions',
    +        description: 'Adding assignee permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-labels-allowed',
    +        name: 'Labels Permissions',
    +        description: 'Adding labels permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-checklists-allowed',
    +        name: 'Checklists Permissions',
    +        description: 'Adding checklists permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-attachments-allowed',
    +        name: 'Attachments Permissions',
    +        description: 'Adding attachments permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-comments-allowed',
    +        name: 'Comments Permissions',
    +        description: 'Adding comments permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-assigned-by-allowed',
    +        name: 'Assigned By Permissions',
    +        description: 'Adding assigned by permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-requested-by-allowed',
    +        name: 'Requested By Permissions',
    +        description: 'Adding requested by permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-activities-allowed',
    +        name: 'Activities Permissions',
    +        description: 'Adding activities permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-description-title-allowed',
    +        name: 'Description Title Permissions',
    +        description: 'Adding description title permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-description-text-allowed',
    +        name: 'Description Text Permissions',
    +        description: 'Adding description text permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-description-text-allowed-on-minicard',
    +        name: 'Minicard Description Permissions',
    +        description: 'Adding minicard description permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-sort-field-to-boards',
    +        name: 'Board Sort Field',
    +        description: 'Adding sort field to boards',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-default-profile-view',
    +        name: 'Default Profile View',
    +        description: 'Setting default profile view',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-hide-logo-by-default',
    +        name: 'Hide Logo Default',
    +        description: 'Setting hide logo as default',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-hide-card-counter-list-by-default',
    +        name: 'Hide Card Counter Default',
    +        description: 'Setting hide card counter as default',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-hide-board-member-list-by-default',
    +        name: 'Hide Board Member List Default',
    +        description: 'Setting hide board member list as default',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-card-number-allowed',
    +        name: 'Card Number Permissions',
    +        description: 'Adding card number permissions',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'assign-boardwise-card-numbers',
    +        name: 'Board Card Numbers',
    +        description: 'Assigning board-wise card numbers',
    +        weight: 3,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'add-card-details-show-lists',
    +        name: 'Card Details Show Lists',
    +        description: 'Adding card details show lists option',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'migrate-attachments-collectionFS-to-ostrioFiles',
    +        name: 'Migrate Attachments to Meteor-Files',
    +        description: 'Migrating attachments from CollectionFS to Meteor-Files',
    +        weight: 8,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'migrate-avatars-collectionFS-to-ostrioFiles',
    +        name: 'Migrate Avatars to Meteor-Files',
    +        description: 'Migrating avatars from CollectionFS to Meteor-Files',
    +        weight: 6,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'migrate-attachment-drop-index-cardId',
    +        name: 'Drop Attachment Index',
    +        description: 'Dropping old attachment index',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'migrate-attachment-migration-fix-source-import',
    +        name: 'Fix Attachment Source Import',
    +        description: 'Fixing attachment source import field',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'attachment-cardCopy-fix-boardId-etc',
    +        name: 'Fix Attachment Card Copy',
    +        description: 'Fixing attachment card copy board IDs',
    +        weight: 2,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'remove-unused-planning-poker',
    +        name: 'Remove Planning Poker',
    +        description: 'Removing unused planning poker fields',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'remove-user-profile-hiddenSystemMessages',
    +        name: 'Remove Hidden System Messages',
    +        description: 'Removing hidden system messages field',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'remove-user-profile-hideCheckedItems',
    +        name: 'Remove Hide Checked Items',
    +        description: 'Removing hide checked items field',
    +        weight: 1,
    +        completed: false,
    +        progress: 0
    +      },
    +      {
    +        id: 'migrate-lists-to-per-swimlane',
    +        name: 'Migrate Lists to Per-Swimlane',
    +        description: 'Migrating lists to per-swimlane structure',
    +        weight: 5,
    +        completed: false,
    +        progress: 0
    +      }
    +    ];
    +  }
    +
    +  /**
    +   * Check if any migrations need to be run for a specific board
    +   */
    +  needsMigration(boardId = null) {
    +    if (boardId) {
    +      // Check if specific board needs migration based on version
    +      const board = ReactiveCache.getBoard(boardId);
    +      return !board || !board.migrationVersion || board.migrationVersion < 1;
    +    }
    +    
    +    // Check if any migration step is not completed (global migrations)
    +    return this.steps.some(step => !step.completed);
    +  }
    +
    +  /**
    +   * Get total weight of all migrations
    +   */
    +  getTotalWeight() {
    +    return this.steps.reduce((total, step) => total + step.weight, 0);
    +  }
    +
    +  /**
    +   * Get completed weight
    +   */
    +  getCompletedWeight() {
    +    return this.steps.reduce((total, step) => {
    +      return total + (step.completed ? step.weight : step.progress * step.weight / 100);
    +    }, 0);
    +  }
    +
    +  /**
    +   * Mark a board as migrated
    +   */
    +  markBoardAsMigrated(boardId) {
    +    try {
    +      Meteor.call('boardMigration.markAsMigrated', boardId, 'full_board_migration', (error, result) => {
    +        if (error) {
    +          console.error('Failed to mark board as migrated:', error);
    +        } else {
    +          console.log('Board marked as migrated:', boardId);
    +        }
    +      });
    +    } catch (error) {
    +      console.error('Error marking board as migrated:', error);
    +    }
    +  }
    +
    +  /**
    +   * Fix boards that are stuck in migration loop
    +   */
    +  fixStuckBoards() {
    +    try {
    +      Meteor.call('boardMigration.fixStuckBoards', (error, result) => {
    +        if (error) {
    +          console.error('Failed to fix stuck boards:', error);
    +        } else {
    +          console.log('Fix stuck boards result:', result);
    +        }
    +      });
    +    } catch (error) {
    +      console.error('Error fixing stuck boards:', error);
    +    }
    +  }
    +
    +  /**
    +   * Start migration process using cron system
    +   */
    +  async startMigration() {
    +    if (isMigrating.get()) {
    +      return; // Already migrating
    +    }
    +
    +    isMigrating.set(true);
    +    migrationSteps.set([...this.steps]);
    +    this.startTime = Date.now();
    +
    +    try {
    +      // Start server-side cron migrations
    +      Meteor.call('cron.startAllMigrations', (error, result) => {
    +        if (error) {
    +          console.error('Failed to start cron migrations:', error);
    +          migrationStatus.set(`Migration failed: ${error.message}`);
    +          isMigrating.set(false);
    +        }
    +      });
    +
    +      // Poll for progress updates
    +      this.pollCronMigrationProgress();
    +
    +    } catch (error) {
    +      console.error('Migration failed:', error);
    +      migrationStatus.set(`Migration failed: ${error.message}`);
    +      isMigrating.set(false);
    +    }
    +  }
    +
    +  /**
    +   * Poll for cron migration progress updates
    +   */
    +  pollCronMigrationProgress() {
    +    const pollInterval = setInterval(() => {
    +      Meteor.call('cron.getMigrationProgress', (error, result) => {
    +        if (error) {
    +          console.error('Failed to get cron migration progress:', error);
    +          clearInterval(pollInterval);
    +          return;
    +        }
    +
    +        if (result) {
    +          migrationProgress.set(result.progress);
    +          migrationStatus.set(result.status);
    +          migrationCurrentStep.set(result.currentStep);
    +          migrationSteps.set(result.steps);
    +          isMigrating.set(result.isMigrating);
    +
    +          // Update local steps
    +          if (result.steps) {
    +            this.steps = result.steps;
    +          }
    +
    +          // If migration is complete, stop polling
    +          if (!result.isMigrating && result.progress === 100) {
    +            clearInterval(pollInterval);
    +            
    +            // Clear status after delay
    +            setTimeout(() => {
    +              migrationStatus.set('');
    +              migrationProgress.set(0);
    +              migrationEstimatedTime.set('');
    +            }, 3000);
    +          }
    +        }
    +      });
    +    }, 1000); // Poll every second
    +  }
    +
    +  /**
    +   * Run a single migration step
    +   */
    +  async runMigrationStep(step) {
    +    // Simulate migration progress
    +    const steps = 10;
    +    for (let i = 0; i <= steps; i++) {
    +      step.progress = (i / steps) * 100;
    +      this.updateProgress();
    +      
    +      // Simulate work
    +      await new Promise(resolve => setTimeout(resolve, 50));
    +    }
    +
    +    // In a real implementation, this would call the actual migration
    +    // For now, we'll simulate the migration
    +    // Running migration step
    +  }
    +
    +  /**
    +   * Update progress variables
    +   */
    +  updateProgress() {
    +    const totalWeight = this.getTotalWeight();
    +    const completedWeight = this.getCompletedWeight();
    +    const progress = Math.round((completedWeight / totalWeight) * 100);
    +    
    +    migrationProgress.set(progress);
    +    migrationSteps.set([...this.steps]);
    +
    +    // Calculate estimated time remaining
    +    if (this.startTime && progress > 0) {
    +      const elapsed = Date.now() - this.startTime;
    +      const rate = progress / elapsed; // progress per millisecond
    +      const remaining = 100 - progress;
    +      const estimatedMs = remaining / rate;
    +      migrationEstimatedTime.set(this.formatTime(estimatedMs));
    +    }
    +  }
    +
    +  /**
    +   * Format time in milliseconds to human readable format
    +   */
    +  formatTime(ms) {
    +    if (ms < 1000) {
    +      return `${Math.round(ms)}ms`;
    +    }
    +
    +    const seconds = Math.floor(ms / 1000);
    +    const minutes = Math.floor(seconds / 60);
    +    const hours = Math.floor(minutes / 60);
    +
    +    if (hours > 0) {
    +      const remainingMinutes = minutes % 60;
    +      const remainingSeconds = seconds % 60;
    +      return `${hours}h ${remainingMinutes}m ${remainingSeconds}s`;
    +    } else if (minutes > 0) {
    +      const remainingSeconds = seconds % 60;
    +      return `${minutes}m ${remainingSeconds}s`;
    +    } else {
    +      return `${seconds}s`;
    +    }
    +  }
    +
    +  /**
    +   * Clear migration cache (for testing)
    +   */
    +  clearCache() {
    +    this.migrationCache.clear();
    +    this.steps.forEach(step => {
    +      step.completed = false;
    +      step.progress = 0;
    +    });
    +  }
    +}
    +
    +// Export singleton instance
    +export const migrationManager = new MigrationManager();
    diff --git a/client/lib/mixins.js b/client/lib/mixins.js
    new file mode 100644
    index 000000000..8d16be539
    --- /dev/null
    +++ b/client/lib/mixins.js
    @@ -0,0 +1 @@
    +Mixins = {};
    diff --git a/client/lib/modal.js b/client/lib/modal.js
    index 08e1b380e..00b6fc4b7 100644
    --- a/client/lib/modal.js
    +++ b/client/lib/modal.js
    @@ -1,5 +1,4 @@
     const closedValue = null;
    -import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
     
     window.Modal = new (class {
       constructor() {
    diff --git a/client/lib/multiSelection.js b/client/lib/multiSelection.js
    index 3597d2a98..853c7114d 100644
    --- a/client/lib/multiSelection.js
    +++ b/client/lib/multiSelection.js
    @@ -91,16 +91,14 @@ MultiSelection = {
     
       activate() {
         if (!this.isActive()) {
    -      this._sidebarWasOpen = Sidebar && Sidebar.isOpen();
    +      this._sidebarWasOpen = Sidebar.isOpen();
           EscapeActions.executeUpTo('detailsPane');
           this._isActive.set(true);
           Tracker.flush();
         }
    -    if (Sidebar) {
    -      Sidebar.setView(this.sidebarView);
    -      if(Utils.isMiniScreen()) {
    -        Sidebar.hide();
    -      }
    +    Sidebar.setView(this.sidebarView);
    +    if(Utils.isMiniScreen()) {
    +      Sidebar.hide();
         }
       },
     
    diff --git a/client/lib/popup.js b/client/lib/popup.js
    index dad029883..6825f7032 100644
    --- a/client/lib/popup.js
    +++ b/client/lib/popup.js
    @@ -61,7 +61,7 @@ window.Popup = new (class {
             openerElement = self._getTopStack().openerElement;
           } else {
             // For Member Settings sub-popups, always start fresh to avoid content mixing
    -        if (popupName.includes('changeLanguage') || popupName.includes('changeAvatar') ||
    +        if (popupName.includes('changeLanguage') || popupName.includes('changeAvatar') || 
                 popupName.includes('editProfile') || popupName.includes('changePassword') ||
                 popupName.includes('invitePeople') || popupName.includes('support')) {
               self._stack = [];
    @@ -187,14 +187,7 @@ window.Popup = new (class {
     
       getOpenerComponent(n=4) {
         const { openerElement } = Template.parentData(n);
    -    if (!openerElement) return null;
    -    const view = Blaze.getView(openerElement);
    -    let current = view;
    -    while (current) {
    -      if (current.templateInstance) return current.templateInstance();
    -      current = current.parentView;
    -    }
    -    return null;
    +    return BlazeComponent.getComponentForElement(openerElement);
       }
     
       // An utility function that returns the top element of the internal stack
    @@ -219,45 +212,40 @@ window.Popup = new (class {
     
           if (Utils.isMiniScreen()) return { left: 0, top: 0 };
     
    -      // If the opener element is missing (e.g., programmatic open), fallback to viewport origin
    -      if (!$element || $element.length === 0) {
    -        return { left: 10, top: 10, maxHeight: $(window).height() - 20 };
    -      }
    -
           const offset = $element.offset();
           // Calculate actual popup width based on CSS: min(380px, 55vw)
           const viewportWidth = $(window).width();
           const viewportHeight = $(window).height();
           const popupWidth = Math.min(380, viewportWidth * 0.55) + 15; // Add 15px for margin
    -
    +      
           // Check if this is an admin panel edit popup
    -      const isAdminEditPopup = $element.hasClass('edit-user') ||
    -                              $element.hasClass('edit-org') ||
    +      const isAdminEditPopup = $element.hasClass('edit-user') || 
    +                              $element.hasClass('edit-org') || 
                                   $element.hasClass('edit-team');
    -
    +      
           if (isAdminEditPopup) {
             // Center the popup horizontally and use full height
             const centeredLeft = (viewportWidth - popupWidth) / 2;
    -
    +        
             return {
               left: Math.max(10, centeredLeft), // Ensure popup doesn't go off screen
               top: 10, // Start from top with small margin
               maxHeight: viewportHeight - 20, // Use full height minus small margins
             };
           }
    -
    +      
           // Calculate available height for popup
           const popupTop = offset.top + $element.outerHeight();
    -
    +      
           // For language popup, don't use dynamic height to avoid overlapping board
           const isLanguagePopup = $element.hasClass('js-change-language');
           let availableHeight, maxPopupHeight;
    -
    +      
           if (isLanguagePopup) {
             // For language popup, position content area below right vertical scrollbar
             const availableHeight = viewportHeight - popupTop - 20; // 20px margin from bottom (near scrollbar)
             const calculatedHeight = Math.min(availableHeight, viewportHeight * 0.5); // Max 50% of viewport
    -
    +        
             return {
               left: Math.min(offset.left, viewportWidth - popupWidth),
               top: popupTop,
    @@ -267,7 +255,7 @@ window.Popup = new (class {
             // For other popups, use the dynamic height calculation
             availableHeight = viewportHeight - popupTop - 20; // 20px margin from bottom
             maxPopupHeight = Math.min(availableHeight, viewportHeight * 0.8); // Max 80% of viewport
    -
    +        
             return {
               left: Math.min(offset.left, viewportWidth - popupWidth),
               top: popupTop,
    @@ -306,7 +294,7 @@ escapeActions.forEach(actionName => {
         () => Popup[actionName](),
         () => Popup.isOpen(),
         {
    -      noClickEscapeOn: '.js-pop-over,.js-open-card-title-popup,.js-open-inlined-form,.textcomplete-dropdown',
    +      noClickEscapeOn: '.js-pop-over,.js-open-card-title-popup,.js-open-inlined-form',
           enabledOnClick: actionName === 'close',
         },
       );
    diff --git a/client/lib/secureDOMPurify.js b/client/lib/secureDOMPurify.js
    index 323c3b6c0..898687dad 100644
    --- a/client/lib/secureDOMPurify.js
    +++ b/client/lib/secureDOMPurify.js
    @@ -4,7 +4,7 @@ import DOMPurify from 'dompurify';
     export function getSecureDOMPurifyConfig() {
       return {
         // Allow common markdown elements including anchor tags
    -    ALLOWED_TAGS: ['a', 'p', 'br', 'strong', 'em', 'u', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'img', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'hr', 'div', 'span', 's'],
    +    ALLOWED_TAGS: ['a', 'p', 'br', 'strong', 'em', 'u', 'h1', 'h2', 'h3', 'h4', 'h5', 'h6', 'ul', 'ol', 'li', 'blockquote', 'pre', 'code', 'img', 'table', 'thead', 'tbody', 'tr', 'th', 'td', 'hr', 'div', 'span'],
         // Allow safe attributes including href for anchor tags
         ALLOWED_ATTR: ['href', 'title', 'alt', 'src', 'width', 'height', 'target', 'rel'],
         // Allow safe protocols for links
    @@ -44,7 +44,7 @@ export function getSecureDOMPurifyConfig() {
                   }
                   return false;
                 }
    -
    +            
                 // Additional check for base64 encoded SVG with script tags
                 if (src.startsWith('data:image/svg+xml;base64,')) {
                   try {
    diff --git a/client/lib/spinner.js b/client/lib/spinner.js
    index ac6ea0c30..f13d8478d 100644
    --- a/client/lib/spinner.js
    +++ b/client/lib/spinner.js
    @@ -4,20 +4,22 @@ Meteor.subscribe('setting');
     
     import { ALLOWED_WAIT_SPINNERS } from '/config/const';
     
    -export function getSpinnerName() {
    -  let ret = 'Bounce';
    -  let defaultWaitSpinner = Meteor.settings.public.WAIT_SPINNER;
    -  if (defaultWaitSpinner && ALLOWED_WAIT_SPINNERS.includes(defaultWaitSpinner)) {
    -    ret = defaultWaitSpinner;
    -  }
    -  let settings = ReactiveCache.getCurrentSetting();
    +export class Spinner extends BlazeComponent {
    +  getSpinnerName() {
    +    let ret = 'Bounce';
    +    let defaultWaitSpinner = Meteor.settings.public.WAIT_SPINNER;
    +    if (defaultWaitSpinner && ALLOWED_WAIT_SPINNERS.includes(defaultWaitSpinner)) {
    +      ret = defaultWaitSpinner;
    +    }
    +    let settings = ReactiveCache.getCurrentSetting();
     
    -  if (settings && settings.spinnerName) {
    -    ret = settings.spinnerName;
    +    if (settings && settings.spinnerName) {
    +      ret = settings.spinnerName;
    +    }
    +    return ret;
       }
    -  return ret;
    -}
     
    -export function getSpinnerTemplate() {
    -  return 'spinner' + getSpinnerName().replace(/-/, '');
    +  getSpinnerTemplate() {
    +    return 'spinner' + this.getSpinnerName().replace(/-/, '');
    +  }
     }
    diff --git a/client/lib/textComplete.js b/client/lib/textComplete.js
    index 8a231b859..e97d38534 100644
    --- a/client/lib/textComplete.js
    +++ b/client/lib/textComplete.js
    @@ -1,80 +1,51 @@
    -// We use @textcomplete packages to integrate with our EscapeActions system.
    -// You should always use `createEscapeableTextComplete` or the jQuery extension
    -// `escapeableTextComplete` instead of the vanilla textcomplete.
    -import { Textcomplete } from '@textcomplete/core';
    -import { TextareaEditor } from '@textcomplete/textarea';
    -import { ContenteditableEditor } from '@textcomplete/contenteditable';
    -
    +// We “inherit” the jquery-textcomplete plugin to integrate with our
    +// EscapeActions system. You should always use `escapeableTextComplete` instead
    +// of the vanilla `textcomplete`.
     let dropdownMenuIsOpened = false;
     
    -/**
    - * Create an escapeable textcomplete instance for a textarea or contenteditable element
    - * @param {HTMLTextAreaElement|HTMLElement} element - The target element
    - * @param {Array} strategies - Array of strategy objects
    - * @param {Object} options - Additional options
    - * @returns {Textcomplete} The textcomplete instance
    - */
    -export function createEscapeableTextComplete(element, strategies, options = {}) {
    -  // Determine the appropriate editor based on element type
    -  const isContentEditable = element.isContentEditable || element.contentEditable === 'true';
    -  const Editor = isContentEditable ? ContenteditableEditor : TextareaEditor;
    -
    -  const editor = new Editor(element);
    -
    -  // Merge default options
    -  const mergedOptions = {
    -    dropdown: {
    -      className: 'textcomplete-dropdown',
    -      maxCount: 10,
    -      placement: 'bottom',
    -      ...options.dropdown,
    -    },
    -  };
    -
    -  const textcomplete = new Textcomplete(editor, strategies, mergedOptions);
    -
    +$.fn.escapeableTextComplete = function(strategies, options, ...otherArgs) {
       // When the autocomplete menu is shown we want both a press of both `Tab`
    -  // or `Enter` to validate the auto-completion. We also need to stop the
    +  // or `Enter` to validation the auto-completion. We also need to stop the
       // event propagation to prevent EscapeActions side effect, for instance the
       // minicard submission (on `Enter`) or going on the next column (on `Tab`).
    -  element.addEventListener('keydown', (evt) => {
    -    if (dropdownMenuIsOpened && (evt.keyCode === 9 || evt.keyCode === 13)) {
    -      evt.stopPropagation();
    -    }
    -  });
    +  options = {
    +    onKeydown(evt, commands) {
    +      if (evt.keyCode === 9 || evt.keyCode === 13) {
    +        evt.stopPropagation();
    +        return commands.KEY_ENTER;
    +      }
    +      return null;
    +    },
    +    ...options,
    +  };
     
    -  // Track dropdown state for EscapeActions integration
    -  // Since @textcomplete automatically closes when Escape is pressed, we
    -  // integrate with our EscapeActions system by tracking open/close state.
    -  textcomplete.on('show', () => {
    -    dropdownMenuIsOpened = true;
    -  });
    +  // Proxy to the vanilla jQuery component
    +  this.textcomplete(strategies, options, ...otherArgs);
     
    -  textcomplete.on('selected', () => {
    -    EscapeActions.preventNextClick();
    -  });
    -
    -  textcomplete.on('hidden', () => {
    -    Tracker.afterFlush(() => {
    -      // XXX Hack. We unfortunately need to set a setTimeout here to make the
    -      // `noClickEscapeOn` work below, otherwise clicking on a autocomplete
    -      // item will close both the autocomplete menu (as expected) but also the
    -      // next item in the stack (for example the minicard editor) which we
    -      // don't want.
    -      setTimeout(() => {
    -        dropdownMenuIsOpened = false;
    -      }, 100);
    -    });
    -  });
    -
    -  return textcomplete;
    -}
    -
    -// jQuery extension for backward compatibility
    -$.fn.escapeableTextComplete = function(strategies, options = {}) {
    -  return this.each(function() {
    -    createEscapeableTextComplete(this, strategies, options);
    +  // Since commit d474017 jquery-textComplete automatically closes a potential
    +  // opened dropdown menu when the user press Escape. This behavior conflicts
    +  // with our EscapeActions system, but it's too complicated and hacky to
    +  // monkey-pach textComplete to disable it -- I tried. Instead we listen to
    +  // 'open' and 'hide' events, and create a ghost escapeAction when the dropdown
    +  // is opened (and rely on textComplete to execute the actual action).
    +  this.on({
    +    'textComplete:show'() {
    +      dropdownMenuIsOpened = true;
    +    },
    +    'textComplete:hide'() {
    +      Tracker.afterFlush(() => {
    +        // XXX Hack. We unfortunately need to set a setTimeout here to make the
    +        // `noClickEscapeOn` work bellow, otherwise clicking on a autocomplete
    +        // item will close both the autocomplete menu (as expected) but also the
    +        // next item in the stack (for example the minicard editor) which we
    +        // don't want.
    +        setTimeout(() => {
    +          dropdownMenuIsOpened = false;
    +        }, 100);
    +      });
    +    },
       });
    +  return this;
     };
     
     EscapeActions.register(
    diff --git a/client/lib/utils.js b/client/lib/utils.js
    index 09d892458..265d589b2 100644
    --- a/client/lib/utils.js
    +++ b/client/lib/utils.js
    @@ -1,16 +1,14 @@
     import { ReactiveCache } from '/imports/reactiveCache';
    -import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
    -import { Tracker } from 'meteor/tracker';
     
     Utils = {
    -  async setBackgroundImage(url) {
    +  setBackgroundImage(url) {
         const currentBoard = Utils.getCurrentBoard();
         if (currentBoard.backgroundImageURL !== undefined) {
           $(".board-wrapper").css({"background":"url(" + currentBoard.backgroundImageURL + ")","background-size":"cover"});
           $(".swimlane,.swimlane .list,.swimlane .list .list-body,.swimlane .list:first-child .list-body").css({"background-color":"transparent"});
           $(".minicard").css({"opacity": "0.9"});
         } else if (currentBoard["background-color"]) {
    -      await currentBoard.setColor(currentBoard["background-color"]);
    +      currentBoard.setColor(currentBoard["background-color"]);
         }
       },
       /** returns the current board id
    @@ -81,21 +79,13 @@ Utils = {
       },
     
       getMobileMode() {
    -    // Check localStorage first - user's explicit preference takes priority
    -    const stored = localStorage.getItem('wekan-mobile-mode');
    -    if (stored !== null) {
    -      return stored === 'true';
    -    }
    -
    -    // Then check user profile
         const user = ReactiveCache.getCurrentUser();
         if (user && user.profile && user.profile.mobileMode !== undefined) {
           return user.profile.mobileMode;
         }
    -
    -    // Default to mobile mode for iPhone/iPod
    -    const isIPhone = /iPhone|iPod/i.test(navigator.userAgent);
    -    return isIPhone;
    +    // For non-logged-in users, check localStorage
    +    const stored = localStorage.getItem('wekan-mobile-mode');
    +    return stored ? stored === 'true' : false;
       },
     
       setMobileMode(enabled) {
    @@ -103,41 +93,13 @@ Utils = {
         if (user) {
           // Update user profile
           user.setMobileMode(enabled);
    +    } else {
    +      // Store in localStorage for non-logged-in users
    +      localStorage.setItem('wekan-mobile-mode', enabled.toString());
         }
    -    // Always store in localStorage for persistence across sessions
    -    localStorage.setItem('wekan-mobile-mode', enabled.toString());
         Utils.applyMobileMode(enabled);
         // Trigger reactive updates for UI components
         Session.set('wekan-mobile-mode', enabled);
    -    // Re-apply zoom level to ensure proper rendering
    -    const zoomLevel = Utils.getZoomLevel();
    -    Utils.applyZoomLevel(zoomLevel);
    -  },
    -
    -  getCardZoom() {
    -    const user = ReactiveCache.getCurrentUser();
    -    if (user && user.profile && user.profile.cardZoom !== undefined) {
    -      return user.profile.cardZoom;
    -    }
    -    const stored = localStorage.getItem('wekan-card-zoom');
    -    return stored ? parseFloat(stored) : 1.0;
    -  },
    -
    -  setCardZoom(level) {
    -    const user = ReactiveCache.getCurrentUser();
    -    if (user) {
    -      user.setCardZoom(level);
    -    }
    -    localStorage.setItem('wekan-card-zoom', level.toString());
    -    Utils.applyCardZoom(level);
    -    Session.set('wekan-card-zoom', level);
    -  },
    -
    -  applyCardZoom(level) {
    -    const cardDetails = document.querySelector('.card-details');
    -    if (cardDetails) {
    -      cardDetails.style.fontSize = `${level}em`;
    -    }
       },
     
       applyZoomLevel(level) {
    @@ -248,20 +210,7 @@ Utils = {
           currentUser &&
           currentUser.isBoardMember() &&
           !currentUser.isCommentOnly() &&
    -      !currentUser.isWorker() &&
    -      !currentUser.isReadOnly() &&
    -      !currentUser.isReadAssignedOnly()
    -    );
    -    return ret;
    -  },
    -  canMoveCard() {
    -    const currentUser = ReactiveCache.getCurrentUser();
    -    const ret = (
    -      currentUser &&
    -      currentUser.isBoardMember() &&
    -      !currentUser.isCommentOnly() &&
    -      !currentUser.isReadOnly() &&
    -      !currentUser.isReadAssignedOnly()
    +      !currentUser.isWorker()
         );
         return ret;
       },
    @@ -270,9 +219,7 @@ Utils = {
         const ret = (
           currentUser &&
           currentUser.isBoardMember() &&
    -      !currentUser.isCommentOnly() &&
    -      !currentUser.isReadOnly() &&
    -      !currentUser.isReadAssignedOnly()
    +      !currentUser.isCommentOnly()
         );
         return ret;
       },
    @@ -284,21 +231,9 @@ Utils = {
         window.location.reload();
       },
       setBoardView(view) {
    -    const currentUser = ReactiveCache.getCurrentUser();
    -
    +    currentUser = ReactiveCache.getCurrentUser();
         if (currentUser) {
    -      // Update localStorage first
    -      window.localStorage.setItem('boardView', view);
    -
    -      // Update user profile via Meteor method
    -      Meteor.call('setBoardView', view, (error) => {
    -        if (error) {
    -          console.error('[setBoardView] Update failed:', error);
    -        } else {
    -          // Reload to apply the view change
    -          Utils.reload();
    -        }
    -      });
    +      ReactiveCache.getCurrentUser().setBoardView(view);
         } else if (view === 'board-view-swimlanes') {
           window.localStorage.setItem('boardView', 'board-view-swimlanes'); //true
           Utils.reload();
    @@ -308,9 +243,6 @@ Utils = {
         } else if (view === 'board-view-cal') {
           window.localStorage.setItem('boardView', 'board-view-cal'); //true
           Utils.reload();
    -    } else if (view === 'board-view-gantt') {
    -      window.localStorage.setItem('boardView', 'board-view-gantt'); //true
    -      Utils.reload();
         } else {
           window.localStorage.setItem('boardView', 'board-view-swimlanes'); //true
           Utils.reload();
    @@ -336,8 +268,6 @@ Utils = {
           return 'board-view-lists';
         } else if (window.localStorage.getItem('boardView') === 'board-view-cal') {
           return 'board-view-cal';
    -    } else if (window.localStorage.getItem('boardView') === 'board-view-gantt') {
    -      return 'board-view-gantt';
         } else {
           window.localStorage.setItem('boardView', 'board-view-swimlanes'); //true
           Utils.reload();
    @@ -345,85 +275,6 @@ Utils = {
         }
       },
     
    -  getListCollapseState(list) {
    -    if (!list) return false;
    -    const key = `collapsedList-${list._id}`;
    -    const sessionVal = Session.get(key);
    -    if (typeof sessionVal === 'boolean') {
    -      return sessionVal;
    -    }
    -
    -    const user = ReactiveCache.getCurrentUser();
    -    let stored = null;
    -    if (user && user.getCollapsedListFromStorage) {
    -      stored = user.getCollapsedListFromStorage(list.boardId, list._id);
    -    } else if (Users.getPublicCollapsedList) {
    -      stored = Users.getPublicCollapsedList(list.boardId, list._id);
    -    }
    -
    -    if (typeof stored === 'boolean') {
    -      Session.setDefault(key, stored);
    -      return stored;
    -    }
    -
    -    const fallback = typeof list.collapsed === 'boolean' ? list.collapsed : false;
    -    Session.setDefault(key, fallback);
    -    return fallback;
    -  },
    -
    -  setListCollapseState(list, collapsed) {
    -    if (!list) return;
    -    const key = `collapsedList-${list._id}`;
    -    Session.set(key, !!collapsed);
    -    const user = ReactiveCache.getCurrentUser();
    -    if (user) {
    -      Meteor.call('setListCollapsedState', list.boardId, list._id, !!collapsed);
    -    } else if (Users.setPublicCollapsedList) {
    -      Users.setPublicCollapsedList(list.boardId, list._id, !!collapsed);
    -    }
    -  },
    -
    -  getSwimlaneCollapseState(swimlane) {
    -    if (!swimlane) return false;
    -    const key = `collapsedSwimlane-${swimlane._id}`;
    -    const sessionVal = Session.get(key);
    -    if (typeof sessionVal === 'boolean') {
    -      return sessionVal;
    -    }
    -
    -    const user = ReactiveCache.getCurrentUser();
    -    let stored = null;
    -    if (user && user.getCollapsedSwimlaneFromStorage) {
    -      stored = user.getCollapsedSwimlaneFromStorage(
    -        swimlane.boardId,
    -        swimlane._id,
    -      );
    -    } else if (Users.getPublicCollapsedSwimlane) {
    -      stored = Users.getPublicCollapsedSwimlane(swimlane.boardId, swimlane._id);
    -    }
    -
    -    if (typeof stored === 'boolean') {
    -      Session.setDefault(key, stored);
    -      return stored;
    -    }
    -
    -    const fallback = typeof swimlane.collapsed === 'boolean' ? swimlane.collapsed : false;
    -    Session.setDefault(key, fallback);
    -    return fallback;
    -  },
    -
    -  setSwimlaneCollapseState(swimlane, collapsed) {
    -    if (!swimlane) return;
    -    const key = `collapsedSwimlane-${swimlane._id}`;
    -    Session.set(key, !!collapsed);
    -    const user = ReactiveCache.getCurrentUser();
    -    if (user) {
    -      Meteor.call('setSwimlaneCollapsedState', swimlane.boardId, swimlane._id, !!collapsed);
    -    } else if (Users.setPublicCollapsedSwimlane) {
    -      Users.setPublicCollapsedSwimlane(swimlane.boardId, swimlane._id, !!collapsed);
    -    }
    -  },
    -
       myCardsSort() {
         let sort = window.localStorage.getItem('myCardsSort');
     
    @@ -503,8 +354,6 @@ Utils = {
             cardId: card._id,
             boardId: board._id,
             slug: board.slug,
    -        swimlaneId: card.swimlaneId,
    -        listId: card.listId,
           })
         );
       },
    @@ -586,7 +435,7 @@ Utils = {
         this.windowResizeDep.depend();
         // Also depend on mobile mode changes to make this reactive
         Session.get('wekan-mobile-mode');
    -
    +    
         // Show mobile view when:
         // 1. Screen width is 800px or less (matches CSS media queries)
         // 2. Mobile phones in portrait mode
    @@ -602,7 +451,7 @@ Utils = {
     
         // Check if user has explicitly set mobile mode preference
         const userMobileMode = this.getMobileMode();
    -
    +    
         // For iPhone: default to mobile view, but respect user's mobile mode toggle preference
         // This ensures all iPhone models (including iPhone 15 Pro Max, 14 Pro Max, etc.) start with mobile view
         // but users can still switch to desktop mode if they prefer
    @@ -657,18 +506,14 @@ Utils = {
     
       // returns if desktop drag handles are enabled
       isShowDesktopDragHandles() {
    -    const currentUser = Meteor.user();
    -    if (currentUser) {
    -      return currentUser.hasShowDesktopDragHandles();
    -    } else {
    -      // For non-logged-in users, check localStorage
    -      return window.localStorage.getItem('showDesktopDragHandles') === 'true';
    -    }
    +    // Always show drag handles on all displays
    +    return true;
       },
     
       // returns if mini screen or desktop drag handles
       isTouchScreenOrShowDesktopDragHandles() {
    -    return Utils.isTouchScreen() || Utils.isShowDesktopDragHandles();
    +    // Always enable drag handles for all displays
    +    return true;
       },
     
       calculateIndexData(prevData, nextData, nItems = 1) {
    @@ -748,24 +593,22 @@ Utils = {
       },
     
       manageCustomUI() {
    -    // Subscribe to custom UI settings (published from server)
    -    Meteor.subscribe('customUI');
    -    // Reactive helper will be called when Settings data changes
    -    Tracker.autorun(() => {
    -      const settings = Settings.findOne({});
    -      if (settings) {
    -        Utils.setCustomUI(settings);
    +    Meteor.call('getCustomUI', (err, data) => {
    +      if (err && err.error[0] === 'var-not-exist') {
    +        Session.set('customUI', false); // siteId || address server not defined
    +      }
    +      if (!err) {
    +        Utils.setCustomUI(data);
           }
         });
       },
     
       setCustomUI(data) {
    -    const productName = (data && data.productName) ? data.productName : 'Wekan';
         const currentBoard = Utils.getCurrentBoard();
         if (currentBoard) {
    -      document.title = `${currentBoard.title} - ${productName}`;
    +      DocHead.setTitle(`${currentBoard.title} - ${data.productName}`);
         } else {
    -      document.title = productName;
    +      DocHead.setTitle(`${data.productName}`);
         }
       },
     
    @@ -799,29 +642,19 @@ Utils = {
       },
     
       manageMatomo() {
    -    // Subscribe to Matomo configuration (published from server)
    -    Meteor.subscribe('matomoConfig');
    -    // Reactive helper will be called when Settings data changes
    -    Tracker.autorun(() => {
    -      const matomo = Session.get('matomo');
    -      if (matomo === undefined) {
    -        const settings = Settings.findOne({});
    -        if (settings && settings.matomoURL && settings.matomoSiteId) {
    -          const matomoConfig = {
    -            address: settings.matomoURL,
    -            siteId: settings.matomoSiteId,
    -            doNotTrack: settings.matomoDoNotTrack || false,
    -            withUserName: settings.matomoWithUserName || false
    -          };
    -          Utils.setMatomo(matomoConfig);
    -        } else {
    -          Session.set('matomo', false);
    +    const matomo = Session.get('matomo');
    +    if (matomo === undefined) {
    +      Meteor.call('getMatomoConf', (err, data) => {
    +        if (err && err.error[0] === 'var-not-exist') {
    +          Session.set('matomo', false); // siteId || address server not defined
             }
    -      } else if (matomo) {
    -        window._paq = window._paq || [];
    -        window._paq.push(['trackPageView']);
    -      }
    -    });
    +        if (!err) {
    +          Utils.setMatomo(data);
    +        }
    +      });
    +    } else if (matomo) {
    +      window._paq.push(['trackPageView']);
    +    }
       },
     
       getTriggerActionDesc(event, tempInstance) {
    diff --git a/config/accounts.js b/config/accounts.js
    index f26a863cc..247627e97 100644
    --- a/config/accounts.js
    +++ b/config/accounts.js
    @@ -1,12 +1,8 @@
     import { TAPi18n } from '/imports/i18n';
    -import { FlowRouter } from 'meteor/ostrio:flow-router-extra';
     
     const passwordField = AccountsTemplates.removeField('password');
     passwordField.autocomplete = 'current-password';
    -passwordField.template = 'passwordInput';
     const emailField = AccountsTemplates.removeField('email');
    -
    -// Don't add current_password to global fields - it should only be used for change password
     let disableRegistration = false;
     let disableForgotPassword = false;
     let passwordLoginEnabled = false;
    @@ -42,16 +38,16 @@ Meteor.call('getOauthDashboardUrl', (_, result) => {
     Meteor.call('isDisableRegistration', (_, result) => {
       if (result) {
         disableRegistration = true;
    -    // Reconfigure to apply the new setting
    -    AccountsTemplates.configure({
    -      forbidClientAccountCreation: true,
    -    });
    +    //console.log('disableRegistration');
    +    //console.log(result);
       }
     });
     
     Meteor.call('isDisableForgotPassword', (_, result) => {
       if (result) {
         disableForgotPassword = true;
    +    //console.log('disableForgotPassword');
    +    //console.log(result);
       }
     });
     
    @@ -73,7 +69,6 @@ AccountsTemplates.addFields([
         required: true,
         minLength: 6,
         autocomplete: 'new-password',
    -    template: 'passwordInput',
       },
       {
         _id: 'invitationcode',
    @@ -93,30 +88,6 @@ AccountsTemplates.configure({
       sendVerificationEmail: true,
       showForgotPasswordLink: !disableForgotPassword,
       forbidClientAccountCreation: disableRegistration,
    -  onSubmitHook(error, state) {
    -    if (error) {
    -      // Display error to user
    -      const errorDiv = document.getElementById('login-error-message');
    -      if (errorDiv) {
    -        let errorMessage = error.reason || error.message || 'Registration failed. Please try again.';
    -        // If there are validation details, show them
    -        if (error.details && typeof error.details === 'object') {
    -          const detailMessages = [];
    -          for (let field in error.details) {
    -            const errorMsg = error.details[field];
    -            if (errorMsg) {
    -              const message = Array.isArray(errorMsg) ? errorMsg.join(', ') : errorMsg;
    -              detailMessages.push(`${field}: ${message}`);
    -            }
    -          }
    -          if (detailMessages.length > 0) {
    -            errorMessage += '
    ' + detailMessages.join('
    '); - } - } - errorDiv.innerHTML = errorMessage; - } - } - }, onLogoutHook() { // here comeslogic for redirect if(oidcRedirectionEnabled) diff --git a/config/router.js b/config/router.js index 1f03580fe..1ab853dbd 100644 --- a/config/router.js +++ b/config/router.js @@ -1,5 +1,4 @@ import { TAPi18n } from '/imports/i18n'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; let previousPath; FlowRouter.triggers.exit([ @@ -12,17 +11,12 @@ FlowRouter.route('/', { name: 'home', triggersEnter: [AccountsTemplates.ensureSignedIn], action() { - // Redirect to sign-in immediately if user is not logged in - if (!Meteor.userId()) { - FlowRouter.go('atSignIn'); - return; - } - Session.set('currentBoard', null); Session.set('currentList', null); Session.set('currentCard', null); Session.set('popupCardId', null); Session.set('popupCardBoardId', null); + Filter.reset(); Session.set('sortBy', ''); EscapeActions.executeAll(); @@ -30,7 +24,7 @@ FlowRouter.route('/', { Utils.manageCustomUI(); Utils.manageMatomo(); - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'boardListHeaderBar', content: 'boardList', }); @@ -54,7 +48,7 @@ FlowRouter.route('/public', { Utils.manageCustomUI(); Utils.manageMatomo(); - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'boardListHeaderBar', content: 'boardList', }); @@ -78,103 +72,16 @@ FlowRouter.route('/accessibility', { Utils.manageCustomUI(); Utils.manageMatomo(); - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'accessibilityHeaderBar', content: 'accessibility', }); }, }); -FlowRouter.route('/support', { - name: 'support', - triggersEnter: [AccountsTemplates.ensureSignedIn], - action() { - Session.set('currentBoard', null); - Session.set('currentList', null); - Session.set('currentCard', null); - Session.set('popupCardId', null); - Session.set('popupCardBoardId', null); - - Filter.reset(); - Session.set('sortBy', ''); - EscapeActions.executeAll(); - - Utils.manageCustomUI(); - Utils.manageMatomo(); - - this.render('defaultLayout', { - headerBar: 'supportHeaderBar', - content: 'support', - }); - }, -}); - -FlowRouter.route('/public', { - name: 'public', - action() { - Session.set('currentBoard', null); - Session.set('currentList', null); - Session.set('currentCard', null); - Session.set('popupCardId', null); - Session.set('popupCardBoardId', null); - - Filter.reset(); - Session.set('sortBy', ''); - EscapeActions.executeAll(); - - Utils.manageCustomUI(); - Utils.manageMatomo(); - - this.render('defaultLayout', { - headerBar: 'supportHeaderBar', - content: 'support', - }); - }, -}); - -// Card route MUST be registered BEFORE board route so it matches first -FlowRouter.route('/b/:boardId/:slug/:cardId', { - name: 'card', - action(params) { - Session.set('currentBoard', params.boardId); - Session.set('currentCard', params.cardId); - Session.set('popupCardId', null); - Session.set('popupCardBoardId', null); - - // In desktop mode, add to openCards array to support multiple cards - const isMobile = Utils.getMobileMode(); - if (!isMobile) { - const openCards = Session.get('openCards') || []; - if (!openCards.includes(params.cardId)) { - openCards.push(params.cardId); - Session.set('openCards', openCards); - } - } - - Utils.manageCustomUI(); - Utils.manageMatomo(); - - this.render('defaultLayout', { - headerBar: 'boardHeaderBar', - content: 'board', - }); - }, -}); - - FlowRouter.route('/b/:id/:slug', { name: 'board', action(params) { - const pathSegments = FlowRouter.current().path.split('/').filter(s => s); - - // If we have 4+ segments (b, boardId, slug, cardId), this is a card view - if (pathSegments.length >= 4) { - return; - } - // If slug contains "/" it means a cardId was matched by this greedy pattern - if (params.slug && params.slug.includes('/')) { - return; - } const currentBoard = params.id; const previousBoard = Session.get('currentBoard'); Session.set('currentBoard', currentBoard); @@ -195,7 +102,27 @@ FlowRouter.route('/b/:id/:slug', { Utils.manageCustomUI(); Utils.manageMatomo(); - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { + headerBar: 'boardHeaderBar', + content: 'board', + }); + }, +}); + +FlowRouter.route('/b/:boardId/:slug/:cardId', { + name: 'card', + action(params) { + EscapeActions.executeUpTo('inlinedForm'); + + Session.set('currentBoard', params.boardId); + Session.set('currentCard', params.cardId); + Session.set('popupCardId', null); + Session.set('popupCardBoardId', null); + + Utils.manageCustomUI(); + Utils.manageMatomo(); + + BlazeLayout.render('defaultLayout', { headerBar: 'boardHeaderBar', content: 'board', }); @@ -215,7 +142,7 @@ FlowRouter.route('/shortcuts', { onCloseGoTo: previousPath, }); } else { - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'shortcutsHeaderBar', content: shortcutsTemplate, }); @@ -240,7 +167,7 @@ FlowRouter.route('/b/templates', { Utils.manageCustomUI(); Utils.manageMatomo(); - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'boardListHeaderBar', content: 'boardList', }); @@ -259,7 +186,7 @@ FlowRouter.route('/my-cards', { Utils.manageCustomUI(); Utils.manageMatomo(); - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'myCardsHeaderBar', content: 'myCards', }); @@ -279,7 +206,7 @@ FlowRouter.route('/due-cards', { Utils.manageCustomUI(); Utils.manageMatomo(); - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'dueCardsHeaderBar', content: 'dueCards', }); @@ -298,11 +225,7 @@ FlowRouter.route('/global-search', { Utils.manageCustomUI(); Utils.manageMatomo(); - - // Set title with product name - const settings = Settings.findOne({}); - const productName = (settings && settings.productName) ? settings.productName : 'Wekan'; - document.title = `${TAPi18n.__('globalSearch-title')} - ${productName}`; + DocHead.setTitle(TAPi18n.__('globalSearch-title')); if (FlowRouter.getQueryParam('q')) { Session.set( @@ -310,7 +233,7 @@ FlowRouter.route('/global-search', { decodeURIComponent(FlowRouter.getQueryParam('q')), ); } - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'globalSearchHeaderBar', content: 'globalSearch', }); @@ -329,9 +252,9 @@ FlowRouter.route('/bookmarks', { Utils.manageCustomUI(); Utils.manageMatomo(); - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'boardListHeaderBar', - content: 'boardList', + content: 'bookmarks', }); }, }); @@ -349,7 +272,7 @@ FlowRouter.route('/broken-cards', { Utils.manageCustomUI(); Utils.manageMatomo(); - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'brokenCardsHeaderBar', content: brokenCardsTemplate, }); @@ -373,7 +296,7 @@ FlowRouter.route('/import/:source', { Filter.reset(); Session.set('sortBy', ''); EscapeActions.executeAll(); - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'importHeaderBar', content: 'import', }); @@ -398,7 +321,7 @@ FlowRouter.route('/setting', { ], action() { Utils.manageCustomUI(); - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'settingHeaderBar', content: 'setting', }); @@ -422,7 +345,7 @@ FlowRouter.route('/information', { }, ], action() { - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'settingHeaderBar', content: 'information', }); @@ -446,7 +369,7 @@ FlowRouter.route('/people', { }, ], action() { - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'settingHeaderBar', content: 'people', }); @@ -470,7 +393,7 @@ FlowRouter.route('/admin-reports', { }, ], action() { - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'settingHeaderBar', content: 'adminReports', }); @@ -494,7 +417,7 @@ FlowRouter.route('/attachments', { }, ], action() { - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'settingHeaderBar', content: 'attachments', }); @@ -518,18 +441,18 @@ FlowRouter.route('/translation', { }, ], action() { - this.render('defaultLayout', { + BlazeLayout.render('defaultLayout', { headerBar: 'settingHeaderBar', content: 'translation', }); }, }); -FlowRouter.route('*', { +FlowRouter.notFound = { action() { - this.render('defaultLayout', { content: 'notFound' }); + BlazeLayout.render('defaultLayout', { content: 'notFound' }); }, -}); +}; // We maintain a list of redirections to ensure that we don't break old URLs // when we change our routing scheme. diff --git a/docker-compose.yml b/docker-compose.yml index 2a004d775..e41ce4e34 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -207,23 +207,21 @@ services: #--------------------------------------------------------------- # ==== OPTIONAL: MONGO OPLOG SETTINGS ===== # https://github.com/wekan/wekan-mongodb/issues/2#issuecomment-378343587 - # HIGHLY RECOMMENDED for pub/sub performance! - # MongoDB oplog is used by Meteor for real-time data synchronization. - # Without oplog, Meteor falls back to polling which increases: - # - CPU usage by 3-5x - # - Network traffic significantly - # - Latency from 50ms to 2000ms - # Must configure MongoDB replica set first - # See: https://blog.meteor.com/tuning-meteor-mongo-livedata-for-scalability-13fe9deb8908 - # For local MongoDB with replicaSet 'rs0': - # - MONGO_OPLOG_URL=mongodb://127.0.0.1:27017/local?replicaSet=rs0 - # For production with authentication: - # - MONGO_OPLOG_URL=mongodb://:@/local?authSource=admin&replicaSet=rsWekan - # Enables: - # - Real-time data updates via DDP (sub-100ms latency) - # - Lower CPU usage and network overhead - # - Better scalability with multiple Wekan instances - # - MONGO_OPLOG_URL=mongodb://127.0.0.1:27017/local?replicaSet=rs0 + # We've fixed our CPU usage problem today with an environment + # change around Wekan. I wasn't aware during implementation + # that if you're using more than 1 instance of Wekan + # (or any MeteorJS based tool) you're supposed to set + # MONGO_OPLOG_URL as an environment variable. + # Without setting it, Meteor will perform a poll-and-diff + # update of it's dataset. With it, Meteor will update from + # the OPLOG. See here + # https://blog.meteor.com/tuning-meteor-mongo-livedata-for-scalability-13fe9deb8908 + # After setting + # MONGO_OPLOG_URL=mongodb://:@/local?authSource=admin&replicaSet=rsWekan + # the CPU usage for all Wekan instances dropped to an average + # of less than 10% with only occasional spikes to high usage + # (I guess when someone is doing a lot of work) + # - MONGO_OPLOG_URL=mongodb://:@/local?authSource=admin&replicaSet=rsWekan #--------------------------------------------------------------- # ==== OPTIONAL: KADIRA PERFORMANCE MONITORING FOR METEOR ==== # https://github.com/edemaine/kadira-compose diff --git a/docker-compose.yml-arm64 b/docker-compose.yml-arm64 new file mode 100644 index 000000000..a57668403 --- /dev/null +++ b/docker-compose.yml-arm64 @@ -0,0 +1,817 @@ +version: '2' + +#--------------------------------------------------------------------------------------------------------- +# ==== START ==== +# docker-compose up -d -f docker-compose.yml-arm64 +#--------------------------------------------------------------------------------------------------------- +# Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required. +#--------------------------------------------------------------------------------------------------------- +# ==== CREATING USERS AND LOGGING IN TO WEKAN ==== +# https://github.com/wekan/wekan/wiki/Adding-users +#--------------------------------------------------------------------------------------------------------- +# ==== FORGOT PASSWORD ==== +# https://github.com/wekan/wekan/wiki/Forgot-Password +#--------------------------------------------------------------------------------------------------------- +# ==== Upgrading Wekan to new version ===== +# NOTE: MongoDB has changed from 3.x to 4.x, in that case you need backup/restore with --noIndexRestore +# see https://github.com/wekan/wekan/wiki/Backup +# 1) Stop Wekan: +# docker-compose stop +# 2) Remove old Wekan app (wekan-app only, not that wekan-db container that has all your data) +# docker rm wekan-app +# 3) Get newest docker-compose.yml from https://github.com/wekan/wekan to have correct image, +# for example: "image: quay.io/wekan/wekan" or version tag "image: quay.io/wekan/wekan:v4.52" +# 4) Start Wekan: +# docker-compose up -d +#---------------------------------------------------------------------------------- +# ==== OPTIONAL: DEDICATED DOCKER USER ==== +# 1) Optionally create a dedicated user for Wekan, for example: +# sudo useradd -d /home/wekan -m -s /bin/bash wekan +# 2) Add this user to the docker group, then logout+login or reboot: +# sudo usermod -aG docker wekan +# 3) Then login as user wekan. +# 4) Create this file /home/wekan/docker-compose.yml with your modifications. +#---------------------------------------------------------------------------------- +# ==== RUN DOCKER AS SERVICE ==== +# 1a) Running Docker as service, on Systemd like Debian 9, Ubuntu 16.04, CentOS 7: +# sudo systemctl enable docker +# sudo systemctl start docker +# 1b) Running Docker as service, on init.d like Debian 8, Ubuntu 14.04, CentOS 6: +# sudo update-rc.d docker defaults +# sudo service docker start +# ---------------------------------------------------------------------------------- +# ==== USAGE OF THIS docker-compose.yml ==== +# 1) For seeing does Wekan work, try this and check with your web browser: +# docker-compose up +# 2) Stop Wekan and start Wekan in background: +# docker-compose stop +# docker-compose up -d +# 3) See running Docker containers: +# docker ps +# 4) Stop Docker containers: +# docker-compose stop +# ---------------------------------------------------------------------------------- +# ===== INSIDE DOCKER CONTAINERS, AND BACKUP/RESTORE ==== +# https://github.com/wekan/wekan/wiki/Backup +# If really necessary, repair MongoDB: https://github.com/wekan/wekan-mongodb/issues/6#issuecomment-424004116 +# 1) Going inside containers: +# a) Wekan app, does not contain data +# docker exec -it wekan-app bash +# b) MongoDB, contains all data +# docker exec -it wekan-db bash +# 2) Copying database to outside of container: +# docker exec -it wekan-db bash +# cd /data +# mongodump +# exit +# docker cp wekan-db:/data/dump . +# 3) Restoring database +# # 1) Stop wekan +# docker stop wekan-app +# # 2) Go inside database container +# docker exec -it wekan-db bash +# # 3) and data directory +# cd /data +# # 4) Remove previous dump +# rm -rf dump +# # 5) Exit db container +# exit +# # 6) Copy dump to inside docker container +# docker cp dump wekan-db:/data/ +# # 7) Go inside database container +# docker exec -it wekan-db bash +# # 8) and data directory +# cd /data +# # 9) Restore +# mongorestore --drop +# # 10) Exit db container +# exit +# # 11) Start wekan +# docker start wekan-app +#------------------------------------------------------------------------- + +services: + + wekandb: + #------------------------------------------------------------------------------------- + # ==== MONGODB FROM DOCKER HUB ==== + image: mongo:6 + #------------------------------------------------------------------------------------- + container_name: wekan-db + restart: always + # command: mongod --oplogSize 128 + # Syslog: mongod --syslog --oplogSize 128 --quiet + # Disable MongoDB logs: + command: mongod --logpath /dev/null --oplogSize 128 --quiet + networks: + - wekan-tier + expose: + - 27017 + volumes: + - /etc/localtime:/etc/localtime:ro + - wekan-db:/data/db + - wekan-db-dump:/dump + #- /etc/timezone:/etc/timezone:ro # Do not use https://github.com/wekan/wekan/issues/5123 + + wekan: + #------------------------------------------------------------------------------------- + # ==== WEKAN FROM GITHUB/QUAY/DOCKER HUB ==== + # All of GitHub, Quay and Docker Hub have latest, but because + # latest tag changes when is newest release, + # when upgrading would be better to use version tag. + # a) Using specific version tag is better: + # image: ghcr.io/wekan/wekan:v6.89 + # image: quay.io/wekan/wekan:v6.89 + # image: wekanteam/wekan:v6.89 + # b) GitHub Container registry. + #image: ghcr.io/wekan/wekan:main + image: ghcr.io/wekan/wekan:v7.09-arm64 + # c) Quay: + #image: quay.io/wekan/wekan:latest + # d) Docker Hub: + #image: wekanteam/wekan:latest + #------------------------------------------------------------------------------------- + container_name: wekan-app + # On CentOS 7 there is seccomp issue with glibc 6, + # so CentOS 7 users shoud use these security_opt seccomp:unconfined + # settings to get WeKan working. See: + # - https://github.com/wekan/wekan/issues/4585 + # - https://github.com/wekan/wekan/issues/4587 + #security_opt: + # - seccomp:unconfined + restart: always + networks: + - wekan-tier + #------------------------------------------------------------------------------------- + # ==== BUILD wekan-app DOCKER CONTAINER FROM SOURCE, if you uncomment these ==== + # ==== and use commands: docker-compose up -d --build + #build: + # context: . + # dockerfile: Dockerfile + #------------------------------------------------------------------------------------- + ports: + # Docker outsideport:insideport. Do not add anything extra here. + # For example, if you want to have wekan on port 3001, + # use 3001:8080 . Do not add any extra address etc here, that way it does not work. + # remove port mapping if you use nginx reverse proxy, port 8080 is already exposed to wekan-tier network + - 80:8080 + environment: + #----------------------------------------------------------------- + # ==== WRITEABLE PATH FOR FILE UPLOADS ==== + - WRITABLE_PATH=/data + #----------------------------------------------------------------- + # ==== AWS S3 FOR FILES ==== + # Any region. For example: + # us-standard,us-west-1,us-west-2, + # eu-west-1,eu-central-1, + # ap-southeast-1,ap-northeast-1,sa-east-1 + # + #- S3='{"s3":{"key": "xxx", "secret": "xxx", "bucket": "xxx", "region": "xxx"}}' + #- S3_SECRET_FILE=/run/secrets/s3_secret + #----------------------------------------------------------------- + # ==== MONGO_URL ==== + - MONGO_URL=mongodb://wekandb:27017/wekan + #- MONGO_URL=mongodb://username:password@wekandb:27017/wekan + #- MONGO_PASSWORD_FILE=/run/secrets/mongo_password + #--------------------------------------------------------------- + # ==== ROOT_URL SETTING ==== + # Change ROOT_URL to your real Wekan URL, for example: + # If you have Caddy/Nginx/Apache providing SSL + # - https://example.com + # - https://boards.example.com + # This can be problematic with avatars https://github.com/wekan/wekan/issues/1776 + # - https://example.com/wekan + # If without https, can be only wekan node, no need for Caddy/Nginx/Apache if you don't need them + # - http://example.com + # - http://boards.example.com + # - http://192.168.1.100 <=== using at local LAN + - ROOT_URL=http://localhost # <=== using only at same laptop/desktop where Wekan is installed + #--------------------------------------------------------------- + # ==== EMAIL SETTINGS ==== + # Email settings are only at MAIL_URL and MAIL_FROM. + # Admin Panel has test button, but it's not used for settings. + # see https://github.com/wekan/wekan/wiki/Troubleshooting-Mail + # For SSL in email, change smtp:// to smtps:// + # NOTE: Special characters need to be url-encoded in MAIL_URL. + # You can encode those characters for example at: https://www.urlencoder.org + #- MAIL_URL=smtp://user:pass@mailserver.example.com:25/ + - MAIL_URL=smtp://:25/?ignoreTLS=true&tls={rejectUnauthorized:false} + - MAIL_FROM=Wekan Notifications + # Currently MAIL_SERVICE is not in use. + #- MAIL_SERVICE=Outlook365 + #- MAIL_SERVICE_USER=firstname.lastname@hotmail.com + #- MAIL_SERVICE_PASSWORD=SecretPassword + #- MAIL_SERVICE_PASSWORD_FILE=/run/secrets/mail_service_password + #--------------------------------------------------------------- + # https://github.com/wekan/wekan/issues/3585#issuecomment-1021522132 + # Add more Node heap, this is done by default at Dockerfile: + # - NODE_OPTIONS="--max_old_space_size=4096" + # Add more stack, this is done at Dockerfile: + # bash -c "ulimit -s 65500; exec node --stack-size=65500 main.js" + #--------------------------------------------------------------- + # ==== OPTIONAL: MONGO OPLOG SETTINGS ===== + # https://github.com/wekan/wekan-mongodb/issues/2#issuecomment-378343587 + # We've fixed our CPU usage problem today with an environment + # change around Wekan. I wasn't aware during implementation + # that if you're using more than 1 instance of Wekan + # (or any MeteorJS based tool) you're supposed to set + # MONGO_OPLOG_URL as an environment variable. + # Without setting it, Meteor will perform a poll-and-diff + # update of it's dataset. With it, Meteor will update from + # the OPLOG. See here + # https://blog.meteor.com/tuning-meteor-mongo-livedata-for-scalability-13fe9deb8908 + # After setting + # MONGO_OPLOG_URL=mongodb://:@/local?authSource=admin&replicaSet=rsWekan + # the CPU usage for all Wekan instances dropped to an average + # of less than 10% with only occasional spikes to high usage + # (I guess when someone is doing a lot of work) + # - MONGO_OPLOG_URL=mongodb://:@/local?authSource=admin&replicaSet=rsWekan + #--------------------------------------------------------------- + # ==== OPTIONAL: KADIRA PERFORMANCE MONITORING FOR METEOR ==== + # https://github.com/edemaine/kadira-compose + # https://github.com/meteor/meteor-apm-agent + # https://blog.meteor.com/kadira-apm-is-now-open-source-490469ffc85f + #- APM_OPTIONS_ENDPOINT=http://:11011 + #- APM_APP_ID= + #- APM_APP_SECRET= + #--------------------------------------------------------------- + # ==== OPTIONAL: LOGS AND STATS ==== + # https://github.com/wekan/wekan/wiki/Logs + # + # Daily export of Wekan changes as JSON to Logstash and ElasticSearch / Kibana (ELK) + # https://github.com/wekan/wekan-logstash + # + # Statistics Python script for Wekan Dashboard + # https://github.com/wekan/wekan-stats + # + # Console, file, and zulip logger on database changes https://github.com/wekan/wekan/pull/1010 + # with fix to replace console.log by winston logger https://github.com/wekan/wekan/pull/1033 + # but there could be bug https://github.com/wekan/wekan/issues/1094 + # + # There is Feature Request: Logging date and time of all activity with summary reports, + # and requesting reason for changing card to other column https://github.com/wekan/wekan/issues/1598 + #--------------------------------------------------------------- + # ==== NUMBER OF SEARCH RESULTS PER PAGE BY DEFAULT ==== + #- RESULTS_PER_PAGE=20 + #--------------------------------------------------------------- + # ==== AFTER OIDC LOGIN, ADD USERS AUTOMATICALLY TO THIS BOARD ID ==== + # https://github.com/wekan/wekan/pull/5098 + #- DEFAULT_BOARD_ID=abcd1234 + #--------------------------------------------------------------- + # ==== WEKAN API AND EXPORT BOARD ==== + # Wekan Export Board works when WITH_API=true. + # https://github.com/wekan/wekan/wiki/REST-API + # https://github.com/wekan/wekan-gogs + # If you disable Wekan API with false, Export Board does not work. + - WITH_API=true + #--------------------------------------------------------------- + # ==== PASSWORD BRUTE FORCE PROTECTION ==== + #https://atmospherejs.com/lucasantoniassi/accounts-lockout + #Defaults below. Uncomment to change. wekan/server/accounts-lockout.js + #- ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURES_BEFORE=3 + #- ACCOUNTS_LOCKOUT_KNOWN_USERS_PERIOD=60 + #- ACCOUNTS_LOCKOUT_KNOWN_USERS_FAILURE_WINDOW=15 + #- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE=3 + #- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_LOCKOUT_PERIOD=60 + #- ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURE_WINDOW=15 + #--------------------------------------------------------------- + # ==== ACCOUNT OPTIONS ==== + # https://docs.meteor.com/api/accounts-multi.html#AccountsCommon-config + # Defaults below. Uncomment to change. wekan/server/accounts-common.js + # - ACCOUNTS_COMMON_LOGIN_EXPIRATION_IN_DAYS=90 + #--------------------------------------------------------------- + # ==== RICH TEXT EDITOR IN CARD COMMENTS ==== + # https://github.com/wekan/wekan/pull/2560 + - RICHER_CARD_COMMENT_EDITOR=false + #--------------------------------------------------------------- + # ==== CARD OPENED, SEND WEBHOOK MESSAGE ==== + # https://github.com/wekan/wekan/issues/2518 + - CARD_OPENED_WEBHOOK_ENABLED=false + #--------------------------------------------------------------- + # ==== Allow configuration to validate uploaded attachments ==== + #-ATTACHMENTS_UPLOAD_EXTERNAL_PROGRAM=/usr/local/bin/avscan {file} + #-ATTACHMENTS_UPLOAD_MIME_TYPES=image/*,text/* + #-ATTACHMENTS_UPLOAD_MAX_SIZE=5000000 + #--------------------------------------------------------------- + # ==== Allow configuration to validate uploaded avatars ==== + #-AVATARS_UPLOAD_EXTERNAL_PROGRAM=/usr/local/bin/avscan {file} + #-AVATARS_UPLOAD_MIME_TYPES=image/* + #-AVATARS_UPLOAD_MAX_SIZE=500000 + #--------------------------------------------------------------- + # ==== Allow to shrink attached/pasted image ==== + # https://github.com/wekan/wekan/pull/2544 + #- MAX_IMAGE_PIXEL=1024 + #- IMAGE_COMPRESS_RATIO=80 + #--------------------------------------------------------------- + # ==== NOTIFICATION TRAY AFTER READ DAYS BEFORE REMOVE ===== + # Number of days after a notification is read before we remove it. + # Default: 2 + #- NOTIFICATION_TRAY_AFTER_READ_DAYS_BEFORE_REMOVE=2 + #--------------------------------------------------------------- + # ==== BIGEVENTS DUE ETC NOTIFICATIONS ===== + # https://github.com/wekan/wekan/pull/2541 + # Introduced a system env var BIGEVENTS_PATTERN default as "NONE", + # so any activityType matches the pattern, system will send out + # notifications to all board members no matter they are watching + # or tracking the board or not. Owner of the wekan server can + # disable the feature by setting this variable to "NONE" or + # change the pattern to any valid regex. i.e. '|' delimited + # activityType names. + # a) Example + #- BIGEVENTS_PATTERN=due + # b) All + #- BIGEVENTS_PATTERN=received|start|due|end + # c) Disabled + - BIGEVENTS_PATTERN=NONE + #--------------------------------------------------------------- + # ==== EMAIL DUE DATE NOTIFICATION ===== + # https://github.com/wekan/wekan/pull/2536 + # System timelines will be showing any user modification for + # dueat startat endat receivedat, also notification to + # the watchers and if any card is due, about due or past due. + # + # Notify due days, default is None, 2 days before and on the event day + #- NOTIFY_DUE_DAYS_BEFORE_AND_AFTER=2,0 + # + # Notify due at hour of day. Default every morning at 8am. Can be 0-23. + # If env variable has parsing error, use default. Notification sent to watchers. + #- NOTIFY_DUE_AT_HOUR_OF_DAY=8 + #----------------------------------------------------------------- + # ==== EMAIL NOTIFICATION TIMEOUT, ms ===== + # Default: 30000 ms = 30s + #- EMAIL_NOTIFICATION_TIMEOUT=30000 + #----------------------------------------------------------------- + # ==== CORS ===== + # CORS: Set Access-Control-Allow-Origin header. + #- CORS=* + # CORS_ALLOW_HEADERS: Set Access-Control-Allow-Headers header. "Authorization,Content-Type" is required for cross-origin use of the API. + #- CORS_ALLOW_HEADERS=Authorization,Content-Type + # CORS_EXPOSE_HEADERS: Set Access-Control-Expose-Headers header. This is not needed for typical CORS situations + #- CORS_EXPOSE_HEADERS=* + #----------------------------------------------------------------- + # ==== MATOMO INTEGRATION ==== + # Optional: Integration with Matomo https://matomo.org that is installed to your server + # The address of the server where Matomo is hosted. + #- MATOMO_ADDRESS=https://example.com/matomo + # The value of the site ID given in Matomo server for Wekan + #- MATOMO_SITE_ID=1 + # The option do not track which enables users to not be tracked by matomo + #- MATOMO_DO_NOT_TRACK=true + # The option that allows matomo to retrieve the username: + #- MATOMO_WITH_USERNAME=true + #----------------------------------------------------------------- + # ==== BROWSER POLICY AND TRUSTED IFRAME URL ==== + # Enable browser policy and allow one trusted URL that can have iframe that has Wekan embedded inside. + # Setting this to false is not recommended, it also disables all other browser policy protections + # and allows all iframing etc. See wekan/server/policy.js + - BROWSER_POLICY_ENABLED=true + # When browser policy is enabled, HTML code at this Trusted URL can have iframe that embeds Wekan inside. + #- TRUSTED_URL=https://intra.example.com + #----------------------------------------------------------------- + # ==== METRICS ALLOWED IP ADDRESSES ==== + # https://github.com/wekan/wekan/wiki/Metrics + #- METRICS_ALLOWED_IP_ADDRESSES=192.168.0.100,192.168.0.200 + #----------------------------------------------------------------- + # ==== OUTGOING WEBHOOKS ==== + # What to send to Outgoing Webhook, or leave out. If commented out the default values will be: cardId,listId,oldListId,boardId,comment,user,card,commentId,swimlaneId,customerField,customFieldValue + #- WEBHOOKS_ATTRIBUTES=cardId,listId,oldListId,boardId,comment,user,card,commentId + #----------------------------------------------------------------- + # ==== Debug OIDC OAuth2 etc ==== + #- DEBUG=true + #--------------------------------------------- + # ==== AUTOLOGIN WITH OIDC/OAUTH2 ==== + # https://github.com/wekan/wekan/wiki/autologin + #- OIDC_REDIRECTION_ENABLED=true + #----------------------------------------------------------------- + # ==== OAUTH2 ORACLE on premise identity manager OIM ==== + #- ORACLE_OIM_ENABLED=true + #----------------------------------------------------------------- + # ==== OAUTH2 AZURE ==== + # https://github.com/wekan/wekan/wiki/Azure + # 1) Register the application with Azure. Make sure you capture + # the application ID as well as generate a secret key. + # 2) Configure the environment variables. This differs slightly + # by installation type, but make sure you have the following: + #- OAUTH2_ENABLED=true + # Optional OAuth2 CA Cert, see https://github.com/wekan/wekan/issues/3299 + #- OAUTH2_CA_CERT=ABCD1234 + # Use OAuth2 ADFS additional changes. Also needs OAUTH2_ENABLED=true setting. + #- OAUTH2_ADFS_ENABLED=false + # OAuth2 login style: popup or redirect. + #- OAUTH2_LOGIN_STYLE=redirect + # Application GUID captured during app registration: + #- OAUTH2_CLIENT_ID=xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx + # Secret key generated during app registration: + #- OAUTH2_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + #- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret + #- OAUTH2_SERVER_URL=https://login.microsoftonline.com/ + #- OAUTH2_AUTH_ENDPOINT=/oauth2/v2.0/authorize + #- OAUTH2_USERINFO_ENDPOINT=https://graph.microsoft.com/oidc/userinfo + #- OAUTH2_TOKEN_ENDPOINT=/oauth2/v2.0/token + # The claim name you want to map to the unique ID field: + #- OAUTH2_ID_MAP=email + # The claim name you want to map to the username field: + #- OAUTH2_USERNAME_MAP=email + # The claim name you want to map to the full name field: + #- OAUTH2_FULLNAME_MAP=name + # The claim name you want to map to the email field: + #- OAUTH2_EMAIL_MAP=email + #----------------------------------------------------------------- + # ==== OAUTH2 Nextcloud ==== + # 1) Register the application with Nextcloud: https://your.nextcloud/index.php/settings/admin/security + # Make sure you capture the application ID as well as generate a secret key. + # Use https://your.wekan/_oauth/oidc for the redirect URI. + # 2) Configure the environment variables. This differs slightly + # by installation type, but make sure you have the following: + #- OAUTH2_ENABLED=true + # OAuth2 login style: popup or redirect. + #- OAUTH2_LOGIN_STYLE=redirect + # Application GUID captured during app registration: + #- OAUTH2_CLIENT_ID=xxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxx + # Secret key generated during app registration: + #- OAUTH2_SECRET=xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx + #- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret + #- OAUTH2_SERVER_URL=https://your-nextcloud.tld + #- OAUTH2_AUTH_ENDPOINT=/index.php/apps/oauth2/authorize + #- OAUTH2_USERINFO_ENDPOINT=/ocs/v2.php/cloud/user?format=json + #- OAUTH2_TOKEN_ENDPOINT=/index.php/apps/oauth2/api/v1/token + # The claim name you want to map to the unique ID field: + #- OAUTH2_ID_MAP=id + # The claim name you want to map to the username field: + #- OAUTH2_USERNAME_MAP=id + # The claim name you want to map to the full name field: + #- OAUTH2_FULLNAME_MAP=display-name + # The claim name you want to map to the email field: + #- OAUTH2_EMAIL_MAP=email + #----------------------------------------------------------------- + # ==== OAUTH2 KEYCLOAK ==== + # https://github.com/wekan/wekan/wiki/Keycloak <== MAPPING INFO, REQUIRED + #- OAUTH2_ENABLED=true + # OAuth2 login style: popup or redirect. + #- OAUTH2_LOGIN_STYLE=redirect + #- OAUTH2_CLIENT_ID= + #- OAUTH2_SERVER_URL=/auth + #- OAUTH2_AUTH_ENDPOINT=/realms//protocol/openid-connect/auth + #- OAUTH2_USERINFO_ENDPOINT=/realms//protocol/openid-connect/userinfo + #- OAUTH2_TOKEN_ENDPOINT=/realms//protocol/openid-connect/token + #- OAUTH2_SECRET= + #- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret + #----------------------------------------------------------------- + # ==== OAUTH2 DOORKEEPER ==== + # https://github.com/wekan/wekan/issues/1874 + # https://github.com/wekan/wekan/wiki/OAuth2 + # Enable the OAuth2 connection + #- OAUTH2_ENABLED=true + # OAuth2 docs: https://github.com/wekan/wekan/wiki/OAuth2 + # OAuth2 login style: popup or redirect. + #- OAUTH2_LOGIN_STYLE=redirect + # OAuth2 Client ID. + #- OAUTH2_CLIENT_ID=abcde12345 + # OAuth2 Secret. + #- OAUTH2_SECRET=54321abcde + #- OAUTH2_SECRET_FILE=/run/secrets/oauth2_secret + # OAuth2 Server URL. + #- OAUTH2_SERVER_URL=https://chat.example.com + # OAuth2 Authorization Endpoint. + #- OAUTH2_AUTH_ENDPOINT=/oauth/authorize + # OAuth2 Userinfo Endpoint. + #- OAUTH2_USERINFO_ENDPOINT=/oauth/userinfo + # OAuth2 Token Endpoint. + #- OAUTH2_TOKEN_ENDPOINT=/oauth/token + # OAUTH2 ID Token Whitelist Fields. + #- OAUTH2_ID_TOKEN_WHITELIST_FIELDS="" + # OAUTH2 Request Permissions. + #- OAUTH2_REQUEST_PERMISSIONS=openid profile email + # OAuth2 ID Mapping + #- OAUTH2_ID_MAP= + # OAuth2 Username Mapping + #- OAUTH2_USERNAME_MAP= + # OAuth2 Fullname Mapping + #- OAUTH2_FULLNAME_MAP= + # OAuth2 Email Mapping + #- OAUTH2_EMAIL_MAP= + #----------------------------------------------------------------- + # ==== LDAP: UNCOMMENT ALL TO ENABLE LDAP ==== + # https://github.com/wekan/wekan/wiki/LDAP + # For Snap settings see https://github.com/wekan/wekan-snap/wiki/Supported-settings-keys + # Most settings work both on Snap and Docker below. + # Note: Do not add single quotes '' to variables. Having spaces still works without quotes where required. + # + # The default authentication method used if a user does not exist to create and authenticate. Can be set as ldap. + # (this is set properly in the Admin Panel, changing this item does not remove Password login option) + #- DEFAULT_AUTHENTICATION_METHOD=ldap + # + # Enable or not the connection by the LDAP + #- LDAP_ENABLE=true + # + # The port of the LDAP server + #- LDAP_PORT=389 + # + # The host server for the LDAP server + #- LDAP_HOST=localhost + # + #----------------------------------------------------------------- + # ==== LDAP AD Simple Auth ==== + # + # Set to true, if you want to connect with Active Directory by Simple Authentication. + # When using AD Simple Auth, LDAP_BASEDN is not needed. + # + # Example: + #- LDAP_AD_SIMPLE_AUTH=true + # + # === LDAP User Authentication === + # + # a) Option to login to the LDAP server with the user's own username and password, instead of + # an administrator key. Default: false (use administrator key). + # + # b) When using AD Simple Auth, set to true, when login user is used for binding, + # and LDAP_BASEDN is not needed. + # + # Example: + #- LDAP_USER_AUTHENTICATION=true + # + # Which field is used to find the user for the user authentication. Default: uid. + #- LDAP_USER_AUTHENTICATION_FIELD=uid + # + # === LDAP Default Domain === + # + # a) In case AD SimpleAuth is configured, the default domain is appended to the given + # loginname for creating the correct username for the bind request to AD. + # + # b) The default domain of the ldap it is used to create email if the field is not map + # correctly with the LDAP_SYNC_USER_DATA_FIELDMAP + # + # Example : + #- LDAP_DEFAULT_DOMAIN=mydomain.com + # + #----------------------------------------------------------------- + # ==== LDAP BASEDN Auth ==== + # + # The base DN for the LDAP Tree + #- LDAP_BASEDN=ou=user,dc=example,dc=org + # + #----------------------------------------------------------------- + # Fallback on the default authentication method + #- LDAP_LOGIN_FALLBACK=false + # + # Reconnect to the server if the connection is lost + #- LDAP_RECONNECT=true + # + # Overall timeout, in milliseconds + #- LDAP_TIMEOUT=10000 + # + # Specifies the timeout for idle LDAP connections in milliseconds + #- LDAP_IDLE_TIMEOUT=10000 + # + # Connection timeout, in milliseconds + #- LDAP_CONNECT_TIMEOUT=10000 + # + # If the LDAP needs a user account to search + #- LDAP_AUTHENTIFICATION=true + # + # The search user DN - You need quotes when you have spaces in parameters + # 2 examples: + #- LDAP_AUTHENTIFICATION_USERDN=CN=ldap admin,CN=users,DC=domainmatter,DC=lan + #- LDAP_AUTHENTIFICATION_USERDN=CN=wekan_adm,OU=serviceaccounts,OU=admin,OU=prod,DC=mydomain,DC=com + # + # The password for the search user + #- LDAP_AUTHENTIFICATION_PASSWORD=pwd + #- LDAP_AUTHENTIFICATION_PASSWORD_FILE=/run/secrets/ldap_auth_password + # + # Enable logs for the module + #- LDAP_LOG_ENABLED=true + # + # If the sync of the users should be done in the background + #- LDAP_BACKGROUND_SYNC=false + # + # LDAP_BACKGROUND_SYNC_INTERVAL : At which interval does the background task sync in milliseconds + # The format must be as specified in: + # https://bunkat.github.io/later/parsers.html#text + #- LDAP_BACKGROUND_SYNC_INTERVAL=every 1 hours + # At which interval does the background task sync in milliseconds. + # Leave this unset, so it uses default, and does not crash. + # https://github.com/wekan/wekan/issues/2354#issuecomment-515305722 + - LDAP_BACKGROUND_SYNC_INTERVAL='' + # + #- LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED=false + # + #- LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS=false + # + # If using LDAPS: LDAP_ENCRYPTION=ssl + #- LDAP_ENCRYPTION=false + # + # The certification for the LDAPS server. Certificate needs to be included in this docker-compose.yml file. + #- LDAP_CA_CERT=-----BEGIN CERTIFICATE-----MIIE+G2FIdAgIC...-----END CERTIFICATE----- + # + # Reject Unauthorized Certificate + #- LDAP_REJECT_UNAUTHORIZED=false + # + # Optional extra LDAP filters. Don't forget the outmost enclosing parentheses if needed + #- LDAP_USER_SEARCH_FILTER= + # + # base (search only in the provided DN), one (search only in the provided DN and one level deep), or sub (search the whole subtree) + #- LDAP_USER_SEARCH_SCOPE=one + # + # Which field is used to find the user, like uid / sAMAccountName + #- LDAP_USER_SEARCH_FIELD=sAMAccountName + # + # Used for pagination (0=unlimited) + #- LDAP_SEARCH_PAGE_SIZE=0 + # + # The limit number of entries (0=unlimited) + #- LDAP_SEARCH_SIZE_LIMIT=0 + # + # Enable group filtering. Note the authenticated ldap user must be able to query all relevant group data with own login data from ldap. + #- LDAP_GROUP_FILTER_ENABLE=false + # + # The object class for filtering. Example: group + #- LDAP_GROUP_FILTER_OBJECTCLASS= + # + # The attribute of a group identifying it. Example: cn + #- LDAP_GROUP_FILTER_GROUP_ID_ATTRIBUTE= + # + # The attribute inside a group object listing its members. Example: member + #- LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE= + # + # The format of the value of LDAP_GROUP_FILTER_GROUP_MEMBER_ATTRIBUTE. Example: 'dn' if the users dn is saved as value into the attribute. + #- LDAP_GROUP_FILTER_GROUP_MEMBER_FORMAT= + # + # The group name (id) that matches all users. + #- LDAP_GROUP_FILTER_GROUP_NAME= + # + # LDAP_UNIQUE_IDENTIFIER_FIELD : This field is sometimes class GUID (Globally Unique Identifier). Example: guid + #- LDAP_UNIQUE_IDENTIFIER_FIELD= + # + # LDAP_UTF8_NAMES_SLUGIFY : Convert the username to utf8 + #- LDAP_UTF8_NAMES_SLUGIFY=true + # + # LDAP_USERNAME_FIELD : Which field contains the ldap username. username / sAMAccountName + #- LDAP_USERNAME_FIELD=sAMAccountName + # + # LDAP_FULLNAME_FIELD : Which field contains the ldap fullname. fullname / sAMAccountName + #- LDAP_FULLNAME_FIELD=fullname + # + #- LDAP_MERGE_EXISTING_USERS=false + # + # Allow existing account matching by e-mail address when username does not match + #- LDAP_EMAIL_MATCH_ENABLE=true + # + # LDAP_EMAIL_MATCH_REQUIRE : require existing account matching by e-mail address when username does match + #- LDAP_EMAIL_MATCH_REQUIRE=true + # + # LDAP_EMAIL_MATCH_VERIFIED : require existing account email address to be verified for matching + #- LDAP_EMAIL_MATCH_VERIFIED=true + # + # LDAP_EMAIL_FIELD : which field contains the LDAP e-mail address + #- LDAP_EMAIL_FIELD=mail + #----------------------------------------------------------------- + #- LDAP_SYNC_USER_DATA=false + # + #- LDAP_SYNC_USER_DATA_FIELDMAP={"cn":"name", "mail":"email"} + # + #- LDAP_SYNC_GROUP_ROLES= + # + # The default domain of the ldap it is used to create email if the field is not map correctly + # with the LDAP_SYNC_USER_DATA_FIELDMAP is defined in setting LDAP_DEFAULT_DOMAIN above. + # + # Enable/Disable syncing of admin status based on ldap groups: + #- LDAP_SYNC_ADMIN_STATUS=true + # + # Comma separated list of admin group names to sync. + #- LDAP_SYNC_ADMIN_GROUPS=group1,group2 + #--------------------------------------------------------------------- + # Login to LDAP automatically with HTTP header. + # In below example for siteminder, at right side of = is header name. + #- HEADER_LOGIN_ID=HEADERUID + #- HEADER_LOGIN_FIRSTNAME=HEADERFIRSTNAME + #- HEADER_LOGIN_LASTNAME=HEADERLASTNAME + #- HEADER_LOGIN_EMAIL=HEADEREMAILADDRESS + #--------------------------------------------------------------------- + # ==== LOGOUT TIMER, probably does not work yet ==== + # LOGOUT_WITH_TIMER : Enables or not the option logout with timer + # example : LOGOUT_WITH_TIMER=true + #- LOGOUT_WITH_TIMER= + # + # LOGOUT_IN : The number of days + # example : LOGOUT_IN=1 + #- LOGOUT_IN= + # + # LOGOUT_ON_HOURS : The number of hours + # example : LOGOUT_ON_HOURS=9 + #- LOGOUT_ON_HOURS= + # + # LOGOUT_ON_MINUTES : The number of minutes + # example : LOGOUT_ON_MINUTES=55 + #- LOGOUT_ON_MINUTES= + #------------------------------------------------------------------- + # Hide password login form + # - PASSWORD_LOGIN_ENABLED=true + #------------------------------------------------------------------- + #- CAS_ENABLED=true + #- CAS_BASE_URL=https://cas.example.com/cas + #- CAS_LOGIN_URL=https://cas.example.com/login + #- CAS_VALIDATE_URL=https://cas.example.com/cas/p3/serviceValidate + #--------------------------------------------------------------------- + #- SAML_ENABLED=true + #- SAML_PROVIDER= + #- SAML_ENTRYPOINT= + #- SAML_ISSUER= + #- SAML_CERT= + #- SAML_IDPSLO_REDIRECTURL= + #- SAML_PRIVATE_KEYFILE= + #- SAML_PUBLIC_CERTFILE= + #- SAML_IDENTIFIER_FORMAT= + #- SAML_LOCAL_PROFILE_MATCH_ATTRIBUTE= + #- SAML_ATTRIBUTES= + #--------------------------------------------------------------------- + # Wait spinner to use + # - WAIT_SPINNER=Bounce + #--------------------------------------------------------------------- + depends_on: + - wekandb + volumes: + - /etc/localtime:/etc/localtime:ro + - wekan-files:/data:rw + secrets: + - ldap_auth_password + - oauth2_secret + - mail_service_password + - mongo_password + - s3_secret + +#--------------------------------------------------------------------------------- +# ==== OPTIONAL: SHARE DATABASE TO OFFICE LAN AND REMOTE VPN ==== +# When using Wekan both at office LAN and remote VPN: +# 1) Have above Wekan docker container config with LAN IP address +# 2) Copy all of above wekan container config below, look above of this part above and all config below it, +# before above depends_on: part: +# +# wekan: +# #------------------------------------------------------------------------------------- +# # ==== MONGODB AND METEOR VERSION ==== +# # a) For Wekan Meteor 1.8.x version at meteor-1.8 branch, ..... +# +# +# and change name to different name like wekan2 or wekanvpn, and change ROOT_URL to server VPN IP +# address. +# 3) This way both Wekan containers can use same MongoDB database +# and see the same Wekan boards. +# 4) You could also add 3rd Wekan container for 3rd network etc. +# EXAMPLE: +# wekan2: +# ....COPY CONFIG FROM ABOVE TO HERE... +# environment: +# - ROOT_URL='http://10.10.10.10' +# ...COPY CONFIG FROM ABOVE TO HERE... +#--------------------------------------------------------------------------------- + +# OPTIONAL NGINX CONFIG FOR REVERSE PROXY +# nginx: +# image: nginx +# container_name: nginx +# restart: always +# networks: +# - wekan-tier +# depends_on: +# - wekan +# ports: +# - 80:80 +# - 443:443 +# volumes: +# - ./nginx/ssl:/etc/nginx/ssl/:ro +# - ./nginx/nginx.conf:/etc/nginx/nginx.conf:ro +## Alternative volume config: +## volumes: +## - ./nginx/nginx.conf:/etc/nginx/conf.d/default.conf:ro +## - ./nginx/ssl/ssl.conf:/etc/nginx/conf.d/ssl/ssl.conf:ro +## - ./nginx/ssl/testvm-ehu.crt:/etc/nginx/conf.d/ssl/certs/mycert.crt:ro +## - ./nginx/ssl/testvm-ehu.key:/etc/nginx/conf.d/ssl/certs/mykey.key:ro +## - ./nginx/ssl/pphrase:/etc/nginx/conf.d/ssl/pphrase:ro + +volumes: + wekan-files: + driver: local + wekan-db: + driver: local + wekan-db-dump: + driver: local + +networks: + wekan-tier: + driver: bridge + +# Docker Compose Secrets +# Create secret files on the host system before running docker-compose up +# Example: echo "your_password_here" > ldap_auth_password.txt +# Then use: docker-compose up -d +secrets: + ldap_auth_password: + file: ./secrets/ldap_auth_password.txt + oauth2_secret: + file: ./secrets/oauth2_secret.txt + mail_service_password: + file: ./secrets/mail_service_password.txt + mongo_password: + file: ./secrets/mongo_password.txt + s3_secret: + file: ./secrets/s3_secret.txt diff --git a/docs/Backup/Backup.md b/docs/Backup/Backup.md index 44848ca0c..b657fb6a6 100644 --- a/docs/Backup/Backup.md +++ b/docs/Backup/Backup.md @@ -1,20 +1,20 @@ [Sandstorm](Sandstorm) - [Sandstorm Backup](Export-from-Wekan-Sandstorm-grain-.zip-file) -# Related +# Upcoming -[Transferring attachments from MongoDB to filesystem, and text from MongoDB to SQLite](https://github.com/wekan/minio-metadata) +[Transferring to Minio and SQLite](https://github.com/wekan/minio-metadata) # Backup Docker [Also see: Upgrading Synology with Wekan quay images](https://github.com/wekan/wekan/issues/3874#issuecomment-867526249) -Note: Do not run `docker compose down` because it could delete data. https://docs.docker.com/compose/reference/down/ +Note: Do not run `docker-compose down` without verifying your docker-compose file, it does not delete the data by default but caution is advised. Refer to https://docs.docker.com/compose/reference/down/. [docker-compose.yml](https://raw.githubusercontent.com/wekan/wekan/master/docker-compose.yml) This presumes your Wekan Docker is currently running with: ```bash -docker compose up -d +docker-compose up -d ``` Backup to directory dump: ```bash @@ -36,20 +36,23 @@ docker start wekan-app ``` # Upgrade Docker Wekan version -1. Check that you use newest [docker-compose.yml](https://raw.githubusercontent.com/wekan/wekan/refs/heads/main/docker-compose.yml) - that has for example: `image: ghcr.io/wekan/wekan:latest` . If you have old docker-compose.yml, copy it's settings like ROOT_URL to newest docker-compose.yml. +## Newest info +https://github.com/wekan/wekan/discussions/5367 + +## Old info + +Note: Do not run `docker-compose down` without verifying your docker-compose file, it does not delete the data by default but caution is advised. Refer to https://docs.docker.com/compose/reference/down/. ```bash -docker compose stop +docker-compose stop docker rm wekan-app -docker compose up -d ``` -2. If you are migrating from Snap to Docker, if there is files at `/var/snap/wekan/common/files` , copy that directory to be at - docker-compose.yml setting path, for example `export WRITABLE_PATH=/data` , copy files directory to be at `/data/files` - with same user:group directory recursive permissions that directory data has, for example: `sudo chown -R user:group data` +a) For example, if you in docker-compose.yml use `image: wekanteam/wekan` or `image: quay.io/wekan/wekan` for latest development version -3. When you open board, if cards or attachments are not visible, click right sidebar / Board Settings / Migrations. - From there, run most migrations, but not migration about `Restore all from archive`, because it would unarchive cards etc from archive. +b) Or in docker-compose.yml change version tag, or use version tag like `image: wekanteam/wekan:v5.50` or `image: quay.io/wekan/wekan:v5.50` +```bash +docker-compose up -d +``` # Backup Wekan Snap to directory dump ```bash @@ -86,73 +89,6 @@ mongorestore --drop --port 27019 sudo snap start wekan.wekan ./snap-settings.sh ``` - -# Upgrade WeKan Snap Stable 6.x to newest WeKan Snap Candidate - -1. Check that you have enough disk space: `df -h` . Also check size of your data: `sudo du -sh /var/snap/wekan/common` . -2. [Backup Snap](#backup-wekan-snap-to-directory-dump) -3. Move WeKan database common directory content elsewhere: -``` -sudo su -snap stop wekan -mkdir /root/common -mv /var/snap/wekan/common/* /root/common/ -``` -4. Change Snap Stable to Snap Candidate: -``` -sudo snap refresh wekan --channel=latest/candidate --amend -``` -5. [Restore Snap](#restore-wekan-snap) -6. Copy back files directory, if it is there: `sudo cp -pR /root/common/files /var/snap/wekan/common/` -7. If you use [Caddy](https://github.com/wekan/wekan/blob/main/docs/Webserver/Caddy.md), that is included in WeKan, edit /var/snap/wekan/Caddyfile to new syntax: -``` -wekan.yourcompany.com { - tls { - load /etc/caddy/certs - alpn http/1.1 - } - reverse_proxy 127.0.0.1:2000 -} -``` -This is if you have WeKan Node.js running at port 2000, for example with these settings: -``` -sudo snap set wekan root-url='https://wekan.yourcompany.com' -sudo snap set wekan port='2000' -sudo snap set wekan caddy-enabled='true' -sudo snap enable wekan -sudo snap start wekan -``` -You can check is caddy, wekan and mongodb running with: -``` -sudo snap services -``` -If you need to disable WeKan included Caddy, because you have system-wide installed Caddy or other webserver: -``` -sudo snap stop wekan.caddy -sudo systemctl disable snap.wekan.caddy -sudo systemctl stop snap.wekan.caddy -``` -7. When you open board, if cards or attachments are not visible, click right sidebar / Board Settings / Migrations. -From there, run most migrations, but not migration about `Restore all from archive`, because it would unarchive cards etc from archive. - -# If upgrade did not work, going back to WeKan Snap Stable 6.09 - -This is only if you have old 6.09 common directory at /root/common . - -``` -sudo su -mkdir /root/common-newest -snap stop wekan -mv /var/snap/wekan/common/* /root/common-newest/ -snap start wekan -snap refresh wekan --channel=latest/stable --amend -snap stop wekan -rm -rf /var/snap/wekan/common/* -mv /root/common/* /var/snap/wekan/common/ -snap start wekan -./snap-settings.sh -``` - # Upgrade Snap manually immediately (usually it updates automatically) ```bash diff --git a/docs/Backup/Upgrade.md b/docs/Backup/Upgrade.md index 4dbfc3ec0..1ca521d5d 100644 --- a/docs/Backup/Upgrade.md +++ b/docs/Backup/Upgrade.md @@ -1,2 +1,15 @@ -Upgrade info at [Backup page](Backup.md) +``` +sudo snap stop wekan.wekan +export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:/snap/wekan/current/lib/x86_64-linux-gnu +export PATH2=$PATH +export PATH=/snap/wekan/current/bin:$PATH +mongodump --port 27019 +sudo snap get wekan > snap-settings.txt +sudo snap stop wekan.mongodb +sudo mv /var/snap/wekan/common . +sudo mkdir /var/snap/wekan/common +sudo snap refresh wekan --channel=latest/candidate +``` + +To be continued... \ No newline at end of file diff --git a/docs/Databases/FerretDB2-PostgreSQL.md b/docs/Databases/FerretDB2-PostgreSQL.md deleted file mode 100644 index 2e81599f6..000000000 --- a/docs/Databases/FerretDB2-PostgreSQL.md +++ /dev/null @@ -1,173 +0,0 @@ -# Install WeKan, FerretDB 2, PostgreSQL - -- https://blog.ferretdb.io/building-project-management-stack-wekan-ferretdb/ - -## WeKan - -- Alternatively, use wekan-app Docker container from https://raw.githubusercontent.com/wekan/wekan/refs/heads/main/docker-compose.yml - -``` -git clone --branch main --depth 1 https://github.com/wekan/wekan.git -cd wekan -sudo apt update -sudo apt install -y build-essential gcc g++ make git curl wget \ -p7zip-full zip unzip unp npm -sudo npm install -g n -export N_NODE_MIRROR=https://github.com/wekan/node-v14-esm/releases/download -sudo -E n 14.21.4 -sudo npm -g install @mapbox/node-pre-gyp -sudo npm -g install meteor@2.16 --unsafe-perm -export PATH=$PATH:$HOME/.meteor -meteor npm install production -meteor build .build --directory --platforms=web.browser -``` - -## Postgres 17 + DocumentDB - -``` -sudo bash -c 'curl -fsSL https://repo.pigsty.io/pig | bash' -pig repo add pgsql -u -pig ext install pg17 -pig ext install documentdb -``` -Edit `/etc/postgresql/17/main/postgresql.conf`, there set -``` -shared_preload_libraries = 'pg_cron, pg_documentdb, pg_documentdb_core' -``` -edit `/etc/postgresql/17/main/pg_hba.conf` , -replace `scram-sha-256` with trust on the host lines for `127.0.0.1/32` and `::1/128` - -Restart PostgreSQL: -``` -sudo service postgresql restart -``` - -## FerretDB - -Download: -``` -curl -L \ -https://github.com/FerretDB/FerretDB/releases/download/v2.7.0/ferretdb-amd64-linux.deb \ --o /tmp/ferretdb-amd64-linux.deb -``` -Install: -``` -sudo apt -y install /tmp/ferretdb-amd64-linux.deb -``` -Edit your `/etc/systemd/system/ferretdb.service` file, -add your username/password pair to the following line: -``` -Environment="FERRETDB_POSTGRESQL_URL=postgres://ferret:DB_PASSWORD_GOES_HERE@127.0.0.1:5432/postgres" -``` -Then enable and start FerretDB: -``` -sudo systemctl enable ferretdb.service -sudo service ferretdb start -``` - -## Initializing the Database -``` -su - -su - postgres -psql -CREATE ROLE ferret WITH PASSWORD 'DB_PASSWORD_GOES_HERE'; -CREATE DATABASE ferretdb; -GRANT ALL PRIVILEGES ON DATABASE ferretdb TO ferret; -ALTER ROLE ferret WITH LOGIN; -CREATE EXTENSION documentdb CASCADE; -GRANT USAGE ON SCHEMA documentdb_api to ferret; -GRANT USAGE ON SCHEMA documentdb_core to ferret; -GRANT USAGE ON SCHEMA documentdb_api_internal to ferret; -GRANT USAGE ON SCHEMA documentdb_api_catalog to ferret; -GRANT INSERT ON TABLE documentdb_api_catalog.collections to ferret; -GRANT ALL ON SCHEMA documentdb_data to ferret; -GRANT documentdb_admin_role to ferret; -``` -## Launching WeKan - -a) At screen: - -``` -export PATH=$HOME/.meteor -cd ~/wekan - -MONGO_URL=mongodb://ferret:DB_PASSWORD_GOES_HERE@127.0.0.1:27017/wekan \ -WRITABLE_PATH=.. WITH_API=true RICHER_CARD_COMMENT_EDITOR=false \ -ROOT_URL=https://wekan.example.com meteor run \ ---exclude-archs web.browser.legacy,web.cordova \ ---port 8080 2>&1 | tee ../wekan-log.`date +%s`.txt -``` - -b) SystemD Service: - -/etc/default/wekan: -``` -NODE_ENV=production -MAIL_FROM="WeKan kanban " -MAIL_URL=smtp://username:password@email-smtp.eu-west-1.amazonaws.com:587?tls={ciphers:"SSLv3"}&secureConnection=false -OAUTH2_AUTH_ENDPOINT=https://accounts.google.com/o/oauth2/v2/auth -OAUTH2_CLIENT_ID=example.apps.googleusercontent.com -OAUTH2_EMAIL_MAP=email -OAUTH2_ENABLED=true -OAUTH2_FULLNAME_MAP=name -OAUTH2_ID_MAP=sub -OAUTH2_REQUEST_PERMISSIONS="openid https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email" -OAUTH2_SECRET=topsecret -OAUTH2_TOKEN_ENDPOINT=https://oauth2.googleapis.com/token -OAUTH2_USERINFO_ENDPOINT=https://openidconnect.googleapis.com/v1/userinfo -OAUTH2_USERNAME_MAP=nickname -MONGO_LOG_DESTINATION=mongodb-log.txt -MONGODB_PORT=27017 -ROOT_URL=https://boards.example.com -WRITABLE_PATH=../files -MONGO_URL=mongodb://ferret:DB_PASSWORD_GOES_HERE@127.0.0.1:27017/wekan -WITH_API=true -RICHER_CARD_COMMENT_EDITOR=false -CARD_OPENED_WEBHOOK_ENABLED=false -BIGEVENTS_PATTERN=NONE -BROWSER_POLICY_ENABLED=true -TRUSTED_URL='' -WEBHOOKS_ATTRIBUTES='' -LDAP_BACKGROUND_SYNC_INTERVAL='' -``` -`/etc/systemd/system/wekan.service` running as user boards, -`sudo adduser boards` and copy files and update permissions -with `sudo chown boards:boards /home/boards -R` -``` -[Unit] -Description=The Wekan Service -After=syslog.target network.target - -[Service] -EnvironmentFile=/etc/default/wekan -User=boards -Group=boards -WorkingDirectory=/home/boards/repos/bundle -ExecStart=/usr/local/bin/node main.js -Restart=on-failure -SuccessExitStatus=143 -StandardOutput=file:/home/boards/repos/wekan-output-log.txt -StandardError=file:/home/boards/repos/wekan-error-log.txt - -[Install] -WantedBy=multi-user.target -``` -Then enable service: -``` -sudo systemctl enable wekan -sudo systemctl start wekan -``` -For SSL/TLS, I run Caddy at front of Node.js: -https://github.com/wekan/wekan/blob/main/docs/Webserver/Caddy.md - -Related is docs about Raspberry Pi: -https://github.com/wekan/wekan/blob/main/docs/Platforms/FOSS/RaspberryPi/Raspberry-Pi.md - -And also about Windows bundle: -https://github.com/wekan/wekan/blob/main/docs/Platforms/Propietary/Windows/Offline.md - -## Notes - -- Machine must have at least 3 gigs of ram. Crashes at npm installing meteor, with 384 megs. -- Machine must have at least 4 gigs of ram. Crashes at meteor build with 3 gigs. -- Be ready to read rebuild-wekan.sh if you want to actually get it running. diff --git a/docs/Databases/Migrations/CODE_CHANGES_SUMMARY.md b/docs/Databases/Migrations/CODE_CHANGES_SUMMARY.md deleted file mode 100644 index 60f085b73..000000000 --- a/docs/Databases/Migrations/CODE_CHANGES_SUMMARY.md +++ /dev/null @@ -1,426 +0,0 @@ -# Key Code Changes - Migration System Improvements - -## File: server/cronMigrationManager.js - -### Change 1: Added Checklists Import (Line 17) -```javascript -// ADDED -import Checklists from '/models/checklists'; -``` - ---- - -## Change 2: Fixed isMigrationNeeded() Default Case (Lines 402-487) - -### BEFORE (problematic): -```javascript -isMigrationNeeded(migrationName) { - switch (migrationName) { - case 'lowercase-board-permission': - // ... checks ... - - // ... other cases ... - - default: - return true; // ❌ PROBLEM: ALL unknown migrations marked as needed! - } -} -``` - -### AFTER (fixed): -```javascript -isMigrationNeeded(migrationName) { - switch (migrationName) { - case 'lowercase-board-permission': - return !!Boards.findOne({ - $or: [ - { permission: 'PUBLIC' }, - { permission: 'Private' }, - { permission: 'PRIVATE' } - ] - }); - - case 'change-attachments-type-for-non-images': - return !!Attachments.findOne({ - $or: [ - { type: { $exists: false } }, - { type: null }, - { type: '' } - ] - }); - - case 'card-covers': - return !!Cards.findOne({ coverId: { $exists: true, $ne: null } }); - - case 'use-css-class-for-boards-colors': - return !!Boards.findOne({ - $or: [ - { color: { $exists: true } }, - { colorClass: { $exists: false } } - ] - }); - - case 'denormalize-star-number-per-board': - return !!Users.findOne({ - 'profile.starredBoards': { $exists: true, $ne: [] } - }); - - case 'add-member-isactive-field': - return !!Boards.findOne({ - members: { - $elemMatch: { isActive: { $exists: false } } - } - }); - - case 'ensure-valid-swimlane-ids': - return !!Cards.findOne({ - $or: [ - { swimlaneId: { $exists: false } }, - { swimlaneId: null }, - { swimlaneId: '' } - ] - }); - - case 'add-swimlanes': { - const boards = Boards.find({}, { fields: { _id: 1 }, limit: 100 }).fetch(); - return boards.some(board => { - const hasSwimlane = Swimlanes.findOne({ boardId: board._id }, { fields: { _id: 1 }, limit: 1 }); - return !hasSwimlane; - }); - } - - case 'add-checklist-items': - return !!Checklists.findOne({ - $or: [ - { items: { $exists: false } }, - { items: null } - ] - }); - - case 'add-card-types': - return !!Cards.findOne({ - $or: [ - { type: { $exists: false } }, - { type: null }, - { type: '' } - ] - }); - - case 'migrate-attachments-collectionFS-to-ostrioFiles': - return false; // Fresh installs use Meteor-Files only - - case 'migrate-avatars-collectionFS-to-ostrioFiles': - return false; // Fresh installs use Meteor-Files only - - case 'migrate-lists-to-per-swimlane': { - const boards = Boards.find({}, { fields: { _id: 1 }, limit: 100 }).fetch(); - return boards.some(board => comprehensiveBoardMigration.needsMigration(board._id)); - } - - default: - return false; // ✅ FIXED: Only run migrations we explicitly check for - } -} -``` - ---- - -## Change 3: Updated executeMigrationStep() (Lines 494-570) - -### BEFORE (simulated execution): -```javascript -async executeMigrationStep(jobId, stepIndex, stepData, stepId) { - const { name, duration } = stepData; - - // Check for specific migrations... - if (stepId === 'denormalize-star-number-per-board') { - await this.executeDenormalizeStarCount(jobId, stepIndex, stepData); - return; - } - - // ... other checks ... - - // ❌ PROBLEM: Simulated progress for unknown migrations - const progressSteps = 10; - for (let i = 0; i <= progressSteps; i++) { - const progress = Math.round((i / progressSteps) * 100); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress, - currentAction: `Executing: ${name} (${progress}%)` - }); - await new Promise(resolve => setTimeout(resolve, duration / progressSteps)); - } -} -``` - -### AFTER (real handlers): -```javascript -async executeMigrationStep(jobId, stepIndex, stepData, stepId) { - const { name, duration } = stepData; - - // Check if this is the star count migration that needs real implementation - if (stepId === 'denormalize-star-number-per-board') { - await this.executeDenormalizeStarCount(jobId, stepIndex, stepData); - return; - } - - // Check if this is the swimlane validation migration - if (stepId === 'ensure-valid-swimlane-ids') { - await this.executeEnsureValidSwimlaneIds(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'migrate-lists-to-per-swimlane') { - await this.executeComprehensiveBoardMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'lowercase-board-permission') { - await this.executeLowercasePermission(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'change-attachments-type-for-non-images') { - await this.executeAttachmentTypeStandardization(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'card-covers') { - await this.executeCardCoversMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'add-member-isactive-field') { - await this.executeMemberActivityMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'add-swimlanes') { - await this.executeAddSwimlanesIdMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'add-card-types') { - await this.executeAddCardTypesMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'migrate-attachments-collectionFS-to-ostrioFiles') { - await this.executeAttachmentMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'migrate-avatars-collectionFS-to-ostrioFiles') { - await this.executeAvatarMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'use-css-class-for-boards-colors') { - await this.executeBoardColorMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'add-checklist-items') { - await this.executeChecklistItemsMigration(jobId, stepIndex, stepData); - return; - } - - // ✅ FIXED: Unknown migration step - log and mark as complete without doing anything - console.warn(`Unknown migration step: ${stepId} - no handler found. Marking as complete without execution.`); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration skipped: No handler for ${stepId}` - }); -} -``` - ---- - -## Change 4: Added New Execute Methods (Lines 1344-1485) - -### executeAvatarMigration() -```javascript -/** - * Execute avatar migration from CollectionFS to Meteor-Files - * In fresh WeKan installations, this migration is not needed - */ -async executeAvatarMigration(jobId, stepIndex, stepData) { - try { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Checking for legacy avatars...' - }); - - // In fresh WeKan installations, avatars use Meteor-Files only - // No CollectionFS avatars exist to migrate - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: 'No legacy avatars found. Using Meteor-Files only.' - }); - - } catch (error) { - console.error('Error executing avatar migration:', error); - cronJobStorage.saveJobError(jobId, { - stepId: 'migrate-avatars-collectionFS-to-ostrioFiles', - stepIndex, - error, - severity: 'error', - context: { operation: 'avatar_migration' } - }); - throw error; - } -} -``` - -### executeBoardColorMigration() -```javascript -/** - * Execute board color CSS classes migration - */ -async executeBoardColorMigration(jobId, stepIndex, stepData) { - try { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Searching for boards with old color format...' - }); - - const boardsNeedingMigration = Boards.find({ - $or: [ - { color: { $exists: true, $ne: null } }, - { color: { $regex: /^(?!css-)/ } } - ] - }, { fields: { _id: 1 } }).fetch(); - - if (boardsNeedingMigration.length === 0) { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: 'No boards need color migration.' - }); - return; - } - - let updated = 0; - const total = boardsNeedingMigration.length; - - for (const board of boardsNeedingMigration) { - try { - const oldColor = Boards.findOne(board._id)?.color; - if (oldColor) { - Boards.update(board._id, { - $set: { colorClass: `css-${oldColor}` }, - $unset: { color: 1 } - }); - updated++; - - const progress = Math.round((updated / total) * 100); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress, - currentAction: `Migrating board colors: ${updated}/${total}` - }); - } - } catch (error) { - console.error(`Failed to update color for board ${board._id}:`, error); - cronJobStorage.saveJobError(jobId, { - stepId: 'use-css-class-for-boards-colors', - stepIndex, - error, - severity: 'warning', - context: { boardId: board._id } - }); - } - } - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration complete: Updated ${updated} board colors` - }); - - } catch (error) { - console.error('Error executing board color migration:', error); - cronJobStorage.saveJobError(jobId, { - stepId: 'use-css-class-for-boards-colors', - stepIndex, - error, - severity: 'error', - context: { operation: 'board_color_migration' } - }); - throw error; - } -} -``` - -### executeChecklistItemsMigration() -```javascript -/** - * Execute checklist items migration - */ -async executeChecklistItemsMigration(jobId, stepIndex, stepData) { - try { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Checking checklists...' - }); - - const checklistsNeedingMigration = Checklists.find({ - $or: [ - { items: { $exists: false } }, - { items: null } - ] - }, { fields: { _id: 1 } }).fetch(); - - if (checklistsNeedingMigration.length === 0) { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: 'All checklists properly configured. No migration needed.' - }); - return; - } - - let updated = 0; - const total = checklistsNeedingMigration.length; - - for (const checklist of checklistsNeedingMigration) { - Checklists.update(checklist._id, { $set: { items: [] } }); - updated++; - - const progress = Math.round((updated / total) * 100); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress, - currentAction: `Initializing checklists: ${updated}/${total}` - }); - } - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration complete: Initialized ${updated} checklists` - }); - - } catch (error) { - console.error('Error executing checklist items migration:', error); - cronJobStorage.saveJobError(jobId, { - stepId: 'add-checklist-items', - stepIndex, - error, - severity: 'error', - context: { operation: 'checklist_items_migration' } - }); - throw error; - } -} -``` - ---- - -## Summary of Changes - -| Change | Type | Impact | Lines | -|--------|------|--------|-------| -| Added Checklists import | Addition | Enables checklist migration | 17 | -| Fixed isMigrationNeeded() default | Fix | Prevents spurious migrations | 487 | -| Added 5 migration checks | Addition | Proper detection for all types | 418-462 | -| Added 3 execute handlers | Addition | Routes migrations to handlers | 545-559 | -| Added 3 execute methods | Addition | Real implementations | 1344-1485 | -| Removed simulated fallback | Deletion | No more fake progress | ~565-576 | - -**Total Changes**: 6 modifications affecting migration system core functionality -**Result**: All 13 migrations now have real detection + real implementations diff --git a/docs/Databases/Migrations/MIGRATION_SYSTEM_IMPROVEMENTS.md b/docs/Databases/Migrations/MIGRATION_SYSTEM_IMPROVEMENTS.md deleted file mode 100644 index 2230c52c3..000000000 --- a/docs/Databases/Migrations/MIGRATION_SYSTEM_IMPROVEMENTS.md +++ /dev/null @@ -1,185 +0,0 @@ -# Migration System Improvements Summary - -## Overview -Comprehensive improvements to the WeKan migration system to ensure migrations only run when needed and show real progress, not simulated progress. - -## Problem Statement -The previous migration system had several issues: -1. **Simulated Progress**: Many migrations were showing simulated progress instead of tracking actual database changes -2. **False Positives**: Fresh WeKan installations were running migrations unnecessarily (no old data to migrate) -3. **Missing Checks**: Some migration types didn't have explicit "needs migration" checks - -## Solutions Implemented - -### 1. Fixed isMigrationNeeded() Default Case -**File**: `server/cronMigrationManager.js` (lines 402-490) - -**Change**: Modified the default case in `isMigrationNeeded()` switch statement: -```javascript -// BEFORE: default: return true; // This caused all unknown migrations to run -// AFTER: default: return false; // Only run migrations we explicitly check for -``` - -**Impact**: -- Prevents spurious migrations on fresh installs -- Only migrations with explicit checks are considered "needed" - -### 2. Added Explicit Checks for All 13 Migration Types - -All migrations now have explicit checks in `isMigrationNeeded()`: - -| Migration ID | Check Logic | Line | -|---|---|---| -| lowercase-board-permission | Check for `permission` field with uppercase values | 404-407 | -| change-attachments-type-for-non-images | Check for attachments with missing `type` field | 408-412 | -| card-covers | Check for cards with `coverId` field | 413-417 | -| use-css-class-for-boards-colors | Check for boards with `color` field | 418-421 | -| denormalize-star-number-per-board | Check for users with `profile.starredBoards` | 422-428 | -| add-member-isactive-field | Check for board members without `isActive` | 429-437 | -| ensure-valid-swimlane-ids | Check for cards without valid `swimlaneId` | 438-448 | -| add-swimlanes | Check if swimlane structures exist | 449-457 | -| add-checklist-items | Check for checklists without `items` array | 458-462 | -| add-card-types | Check for cards without `type` field | 463-469 | -| migrate-attachments-collectionFS-to-ostrioFiles | Return false (fresh installs use Meteor-Files) | 470-473 | -| migrate-avatars-collectionFS-to-ostrioFiles | Return false (fresh installs use Meteor-Files) | 474-477 | -| migrate-lists-to-per-swimlane | Check if boards need per-swimlane migration | 478-481 | - -### 3. All Migrations Now Use REAL Progress Tracking - -Each migration implementation uses actual database queries and counts: - -**Example - Board Color Migration** (`executeBoardColorMigration`): -```javascript -// Real check - finds boards that actually need migration -const boardsNeedingMigration = Boards.find({ - $or: [ - { color: { $exists: true, $ne: null } }, - { color: { $regex: /^(?!css-)/ } } - ] -}, { fields: { _id: 1 } }).fetch(); - -// Real progress tracking -for (const board of boardsNeedingMigration) { - Boards.update(board._id, { $set: { colorClass: `css-${board.color}` } }); - updated++; - - const progress = Math.round((updated / total) * 100); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress, - currentAction: `Migrating board colors: ${updated}/${total}` - }); -} -``` - -### 4. Implementation Methods Added/Updated - -#### New Methods: -- **`executeAvatarMigration()`** (line 1344): Checks for legacy avatars, returns immediately for fresh installs -- **`executeBoardColorMigration()`** (line 1375): Converts old color format to CSS classes with real progress -- **`executeChecklistItemsMigration()`** (line 1432): Initializes checklist items array with real progress - -#### Updated Methods (all with REAL implementations): -- `executeLowercasePermission()` - Converts board permissions to lowercase -- `executeAttachmentTypeStandardization()` - Updates attachment types with counts -- `executeCardCoversMigration()` - Migrates card cover data with progress tracking -- `executeMemberActivityMigration()` - Adds `isActive` field to board members -- `executeAddSwimlanesIdMigration()` - Adds swimlaneId to cards -- `executeAddCardTypesMigration()` - Adds type field to cards -- `executeAttachmentMigration()` - Migrates attachments from CollectionFS -- `executeDenormalizeStarCount()` - Counts and denormalizes starred board data -- `executeEnsureValidSwimlaneIds()` - Validates swimlane references -- `executeComprehensiveBoardMigration()` - Handles per-swimlane migration - -### 5. Removed Simulated Execution Fallback - -**File**: `server/cronMigrationManager.js` (lines 556-567) - -**Change**: Removed the simulated progress fallback and replaced with a warning: -```javascript -// BEFORE: Simulated 10-step progress for unknown migrations -// AFTER: -console.warn(`Unknown migration step: ${stepId} - no handler found.`); -cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration skipped: No handler for ${stepId}` -}); -``` - -**Impact**: -- No more simulated work for unknown migrations -- Clear logging if a migration type is not recognized -- All migrations show real progress or properly report as not needed - -### 6. Added Missing Import - -**File**: `server/cronMigrationManager.js` (line 17) - -Added import for Checklists model: -```javascript -import Checklists from '/models/checklists'; -``` - -## Migration Behavior on Fresh Install - -When WeKan is freshly installed: -1. Each migration's `isMigrationNeeded()` is called -2. Checks run for actual old data structures -3. No old structures found → `isMigrationNeeded()` returns `false` -4. Migrations are skipped efficiently without unnecessary database work -5. Example log: "All checklists properly configured. No migration needed." - -## Migration Behavior on Old Database - -When WeKan starts with an existing database containing old structures: -1. Each migration's `isMigrationNeeded()` is called -2. Checks find old data structures present -3. `isMigrationNeeded()` returns `true` -4. Migration handler executes with real progress tracking -5. Actual database records are updated with real counts -6. Progress shown: "Migrating X records (50/100)" - -## Benefits - -✅ **No Unnecessary Work**: Fresh installs skip all migrations immediately -✅ **Real Progress**: All shown progress is based on actual database operations -✅ **Clear Logging**: Each step logs what's happening -✅ **Error Tracking**: Failed records are logged with context -✅ **Transparent**: No simulated execution hiding what's actually happening -✅ **Safe**: All 13 migration types have explicit handlers - -## Testing Checklist - -- [ ] Fresh WeKan install shows all migrations as "not needed" -- [ ] No migrations execute on fresh database -- [ ] Old database with legacy data triggers migrations -- [ ] Migration progress shows real record counts -- [ ] All migrations complete successfully -- [ ] Migration errors are properly logged with context -- [ ] Admin panel shows accurate migration status - -## Files Modified - -- `server/cronMigrationManager.js` - Core migration system with all improvements -- `client/components/swimlanes/swimlanes.js` - Drag-to-empty-swimlane feature (previous work) - -## Migration Types Summary - -The WeKan migration system now properly manages 13 migration types: - -| # | Type | Purpose | Real Progress | -|---|------|---------|---| -| 1 | lowercase-board-permission | Standardize board permissions | ✅ Yes | -| 2 | change-attachments-type | Set attachment types | ✅ Yes | -| 3 | card-covers | Denormalize card cover data | ✅ Yes | -| 4 | use-css-class-for-boards-colors | Convert colors to CSS | ✅ Yes | -| 5 | denormalize-star-number-per-board | Count board stars | ✅ Yes | -| 6 | add-member-isactive-field | Add member activity tracking | ✅ Yes | -| 7 | ensure-valid-swimlane-ids | Validate swimlane refs | ✅ Yes | -| 8 | add-swimlanes | Initialize swimlane structure | ✅ Yes | -| 9 | add-checklist-items | Initialize checklist items | ✅ Yes | -| 10 | add-card-types | Set card types | ✅ Yes | -| 11 | migrate-attachments-collectionFS | Migrate attachments | ✅ Yes | -| 12 | migrate-avatars-collectionFS | Migrate avatars | ✅ Yes | -| 13 | migrate-lists-to-per-swimlane | Per-swimlane structure | ✅ Yes | - -All migrations now have real implementations with actual progress tracking! diff --git a/docs/Databases/Migrations/MIGRATION_SYSTEM_REVIEW_COMPLETE.md b/docs/Databases/Migrations/MIGRATION_SYSTEM_REVIEW_COMPLETE.md deleted file mode 100644 index a48e8dde7..000000000 --- a/docs/Databases/Migrations/MIGRATION_SYSTEM_REVIEW_COMPLETE.md +++ /dev/null @@ -1,232 +0,0 @@ -# WeKan Migration System - Comprehensive Review Complete ✅ - -## Executive Summary - -The WeKan migration system has been comprehensively reviewed and improved to ensure: -- ✅ Migrations only run when needed (real data to migrate exists) -- ✅ Progress shown is REAL, not simulated -- ✅ Fresh installs skip all migrations efficiently -- ✅ Old databases detect and run real migrations with actual progress tracking -- ✅ All 13 migration types have proper detection and real implementations - -## What Was Fixed - -### 1. **Default Case Prevention** -**Problem**: Default case in `isMigrationNeeded()` returned `true`, causing all unknown migrations to run -**Solution**: Changed default from `return true` to `return false` -**Impact**: Only migrations we explicitly check for will run - -### 2. **Comprehensive Migration Checks** -**Problem**: Some migration types lacked explicit "needs migration" detection -**Solution**: Added explicit checks for all 13 migration types in `isMigrationNeeded()` -**Impact**: Each migration now properly detects if it's actually needed - -### 3. **Real Progress Tracking** -**Problem**: Many migrations were showing simulated progress instead of actual work -**Solution**: Implemented real database query-based progress for all migrations -**Impact**: Progress percentages reflect actual database operations - -### 4. **Removed Simulated Execution** -**Problem**: Fallback code was simulating work for unknown migrations -**Solution**: Replaced with warning log and immediate completion marker -**Impact**: No more fake work being shown to users - -### 5. **Added Missing Model Import** -**Problem**: Checklists model was used but not imported -**Solution**: Added `import Checklists from '/models/checklists'` -**Impact**: Checklist migration can now work properly - -## Migration System Architecture - -### isMigrationNeeded() - Detection Layer -Located at lines 402-487 in `server/cronMigrationManager.js` - -Each migration type has a case statement that: -1. Queries the database for old/incomplete data structures -2. Returns `true` if migration is needed, `false` if not needed -3. Fresh installs return `false` (no old data structures exist) -4. Old databases return `true` when old structures are found - -### executeMigrationStep() - Routing Layer -Located at lines 494-570 in `server/cronMigrationManager.js` - -Each migration type has: -1. An `if` statement checking the stepId -2. A call to its specific execute method -3. Early return to prevent fallthrough - -### Execute Methods - Implementation Layer -Located at lines 583-1485+ in `server/cronMigrationManager.js` - -Each migration implementation: -1. Queries database for records needing migration -2. Updates cronJobStorage with progress -3. Iterates through records with real counts -4. Handles errors with context logging -5. Reports completion with total records migrated - -## All 13 Migration Types - Status Report - -| # | ID | Name | Detection Check | Handler | Real Progress | -|---|----|----|---|---|---| -| 1 | lowercase-board-permission | Board Permission Standardization | Lines 404-407 | executeLowercasePermission() | ✅ Yes | -| 2 | change-attachments-type-for-non-images | Attachment Type Standardization | Lines 408-412 | executeAttachmentTypeStandardization() | ✅ Yes | -| 3 | card-covers | Card Covers System | Lines 413-417 | executeCardCoversMigration() | ✅ Yes | -| 4 | use-css-class-for-boards-colors | Board Color CSS Classes | Lines 418-421 | executeBoardColorMigration() | ✅ Yes | -| 5 | denormalize-star-number-per-board | Board Star Counts | Lines 422-428 | executeDenormalizeStarCount() | ✅ Yes | -| 6 | add-member-isactive-field | Member Activity Status | Lines 429-437 | executeMemberActivityMigration() | ✅ Yes | -| 7 | ensure-valid-swimlane-ids | Validate Swimlane IDs | Lines 438-448 | executeEnsureValidSwimlaneIds() | ✅ Yes | -| 8 | add-swimlanes | Swimlanes System | Lines 449-457 | executeAddSwimlanesIdMigration() | ✅ Yes | -| 9 | add-checklist-items | Checklist Items | Lines 458-462 | executeChecklistItemsMigration() | ✅ Yes | -| 10 | add-card-types | Card Types | Lines 463-469 | executeAddCardTypesMigration() | ✅ Yes | -| 11 | migrate-attachments-collectionFS-to-ostrioFiles | Migrate Attachments | Lines 470-473 | executeAttachmentMigration() | ✅ Yes | -| 12 | migrate-avatars-collectionFS-to-ostrioFiles | Migrate Avatars | Lines 474-477 | executeAvatarMigration() | ✅ Yes | -| 13 | migrate-lists-to-per-swimlane | Migrate Lists Per-Swimlane | Lines 478-481 | executeComprehensiveBoardMigration() | ✅ Yes | - -**Status**: ALL 13 MIGRATIONS HAVE PROPER DETECTION + REAL IMPLEMENTATIONS ✅ - -## Examples of Real Progress Implementation - -### Example 1: Board Color Migration -```javascript -// REAL check - finds boards that actually need migration -const boardsNeedingMigration = Boards.find({ - $or: [ - { color: { $exists: true, $ne: null } }, - { color: { $regex: /^(?!css-)/ } } - ] -}, { fields: { _id: 1 } }).fetch(); - -if (boardsNeedingMigration.length === 0) { - // Real result - no migration needed - return; -} - -// REAL progress tracking with actual counts -for (const board of boardsNeedingMigration) { - Boards.update(board._id, { $set: { colorClass: `css-${board.color}` } }); - updated++; - - const progress = Math.round((updated / total) * 100); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress, - currentAction: `Migrating board colors: ${updated}/${total}` // Real counts! - }); -} -``` - -### Example 2: Checklist Items Migration -```javascript -// REAL check - finds checklists without items -const checklistsNeedingMigration = Checklists.find({ - $or: [ - { items: { $exists: false } }, - { items: null } - ] -}, { fields: { _id: 1 } }).fetch(); - -if (checklistsNeedingMigration.length === 0) { - // Real result - currentAction: 'All checklists properly configured. No migration needed.' - return; -} - -// REAL progress with actual counts -for (const checklist of checklistsNeedingMigration) { - Checklists.update(checklist._id, { $set: { items: [] } }); - updated++; - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: Math.round((updated / total) * 100), - currentAction: `Initializing checklists: ${updated}/${total}` // Real counts! - }); -} -``` - -## Behavior on Different Database States - -### 🆕 Fresh WeKan Installation -1. Database created with correct schema per models/ -2. Migration system starts -3. For EACH of 13 migrations: - - `isMigrationNeeded()` queries for old data - - No old structures found - - Returns `false` - - Migration is skipped (not even started) -4. **Result**: All migrations marked "not needed" - efficient and clean! - -### 🔄 Old WeKan Database with Legacy Data -1. Database has old data structures -2. Migration system starts -3. For migrations with old data: - - `isMigrationNeeded()` detects old structures - - Returns `true` - - Migration handler executes - - Real progress shown with actual record counts - - "Migrating board colors: 45/120" (real counts!) -4. For migrations without old data: - - `isMigrationNeeded()` finds no old structures - - Returns `false` - - Migration skipped -5. **Result**: Only needed migrations run, with real progress! - -## Files Modified - -| File | Changes | Lines | -|------|---------|-------| -| `server/cronMigrationManager.js` | Added Checklists import, fixed isMigrationNeeded() default, added 5 migration checks, added 3 execute handlers, added 3 implementations, removed simulated fallback | 17, 404-487, 494-570, 1344-1485 | -| `client/components/swimlanes/swimlanes.js` | Added drag-to-empty-swimlane feature (previous work) | - | - -## Verification Results - -✅ All checks pass - run `bash verify-migrations.sh` to verify - -``` -✓ Check 1: Default case returns false -✓ Check 2: All 13 migrations have isMigrationNeeded() checks -✓ Check 3: All migrations have execute() handlers -✓ Check 4: Checklists model is imported -✓ Check 5: Simulated execution removed -✓ Check 6: Real database implementations found -``` - -## Testing Recommendations - -### For Fresh Install: -1. Start fresh WeKan instance -2. Check Admin Panel → Migrations -3. Verify all migrations show "Not needed" or skip immediately -4. Check server logs - should see "All X properly configured" messages -5. No actual database modifications should occur - -### For Old Database: -1. Start WeKan with legacy database -2. Check Admin Panel → Migrations -3. Verify migrations with old data run -4. Progress should show real counts: "Migrating X: 45/120" -5. Verify records are actually updated in database -6. Check server logs for actual operation counts - -### For Error Handling: -1. Verify error logs include context (boardId, cardId, etc.) -2. Verify partial migrations don't break system -3. Verify migration can be re-run if interrupted - -## Performance Impact - -- ✅ Fresh installs: FASTER (migrations skipped entirely) -- ✅ Old databases: SAME (actual work required regardless) -- ✅ Migration status: CLEARER (real progress reported) -- ✅ CPU usage: LOWER (no simulated work loops) - -## Conclusion - -The WeKan migration system now: -- ✅ Only runs migrations when needed (real data to migrate) -- ✅ Shows real progress based on actual database operations -- ✅ Skips unnecessary migrations on fresh installs -- ✅ Handles all 13 migration types with proper detection and implementation -- ✅ Provides clear logging and error context -- ✅ No more simulated execution or false progress reports - -The system is now **transparent, efficient, and reliable**. diff --git a/docs/Databases/Migrations/SESSION_SUMMARY.md b/docs/Databases/Migrations/SESSION_SUMMARY.md deleted file mode 100644 index 241920cbb..000000000 --- a/docs/Databases/Migrations/SESSION_SUMMARY.md +++ /dev/null @@ -1,190 +0,0 @@ -# ✅ Migration System Comprehensive Review - COMPLETE - -## Session Summary - -This session completed a comprehensive review and improvement of the WeKan migration system to ensure migrations only run when needed and show real progress, not simulated progress. - -## What Was Accomplished - -### 1. Migration System Core Fixes (server/cronMigrationManager.js) -✅ **Added Checklists Import** (Line 17) -- Fixed: Checklists model was used but not imported -- Now: `import Checklists from '/models/checklists';` - -✅ **Fixed isMigrationNeeded() Default Case** (Line 487) -- Changed: `default: return true;` → `default: return false;` -- Impact: Prevents spurious migrations on fresh installs -- Only migrations with explicit checks run - -✅ **Added 5 New Migration Checks** (Lines 404-487) -- `use-css-class-for-boards-colors` - Checks for old color format -- `ensure-valid-swimlane-ids` - Checks for cards without swimlaneId -- `add-checklist-items` - Checks for checklists without items array -- `migrate-avatars-collectionFS-to-ostrioFiles` - Returns false (fresh installs) -- `migrate-lists-to-per-swimlane` - Comprehensive board migration detection - -✅ **Added 3 Execute Method Handlers** (Lines 494-570) -- Routes migrations to their specific execute methods -- Removed simulated execution fallback -- Added warning for unknown migrations - -✅ **Added 3 Real Execute Methods** (Lines 1344-1485) -- `executeAvatarMigration()` - Checks for legacy avatars (0 on fresh install) -- `executeBoardColorMigration()` - Converts colors to CSS with real progress -- `executeChecklistItemsMigration()` - Initializes items with real progress tracking - -### 2. Verification & Documentation - -✅ **Created Verification Script** (verify-migrations.sh) -- Checks all 13 migrations have proper implementations -- Verifies default case returns false -- All checks PASS ✅ - -✅ **Created Comprehensive Documentation** -- [MIGRATION_SYSTEM_IMPROVEMENTS.md](MIGRATION_SYSTEM_IMPROVEMENTS.md) -- [MIGRATION_SYSTEM_REVIEW_COMPLETE.md](MIGRATION_SYSTEM_REVIEW_COMPLETE.md) -- [CODE_CHANGES_SUMMARY.md](CODE_CHANGES_SUMMARY.md) - -### 3. Previous Work (Earlier in Session) -✅ **Drag-to-Empty-Swimlane Feature** -- File: client/components/swimlanes/swimlanes.js -- Added `dropOnEmpty: true` to sortable configuration -- Allows dropping lists into empty swimlanes - -## All 13 Migrations - Status - -| # | Type | Detection | Handler | Real Progress | -|---|------|-----------|---------|---| -| 1 | lowercase-board-permission | ✅ Yes | ✅ Yes | ✅ Yes | -| 2 | change-attachments-type | ✅ Yes | ✅ Yes | ✅ Yes | -| 3 | card-covers | ✅ Yes | ✅ Yes | ✅ Yes | -| 4 | use-css-class-for-boards-colors | ✅ Yes | ✅ Yes | ✅ Yes | -| 5 | denormalize-star-number-per-board | ✅ Yes | ✅ Yes | ✅ Yes | -| 6 | add-member-isactive-field | ✅ Yes | ✅ Yes | ✅ Yes | -| 7 | ensure-valid-swimlane-ids | ✅ Yes | ✅ Yes | ✅ Yes | -| 8 | add-swimlanes | ✅ Yes | ✅ Yes | ✅ Yes | -| 9 | add-checklist-items | ✅ Yes | ✅ Yes | ✅ Yes | -| 10 | add-card-types | ✅ Yes | ✅ Yes | ✅ Yes | -| 11 | migrate-attachments-collectionFS | ✅ Yes | ✅ Yes | ✅ Yes | -| 12 | migrate-avatars-collectionFS | ✅ Yes | ✅ Yes | ✅ Yes | -| 13 | migrate-lists-to-per-swimlane | ✅ Yes | ✅ Yes | ✅ Yes | - -**Status: 100% Complete** ✅ - -## Key Improvements - -✅ **Fresh WeKan Install Behavior** -- Each migration checks for old data -- No old structures found = skipped (not wasted time) -- "All X properly configured. No migration needed." messages -- Zero unnecessary database work - -✅ **Old WeKan Database Behavior** -- Migrations detect old data structures -- Run real database updates with actual counts -- "Migrating X records: 45/120" (real progress) -- Proper error logging with context - -✅ **Performance Impact** -- Fresh installs: FASTER (no unnecessary migrations) -- Old databases: SAME (work required regardless) -- CPU usage: LOWER (no simulated work loops) -- Network traffic: SAME (only needed operations) - -## Verification Results - -```bash -$ bash verify-migrations.sh - -✓ Check 1: Default case returns false - PASS -✓ Check 2: All 13 migrations have checks - PASS (13/13) -✓ Check 3: All migrations have execute methods - PASS (13/13) -✓ Check 4: Checklists model imported - PASS -✓ Check 5: Simulated execution removed - PASS -✓ Check 6: Real database implementations - PASS (4 found) - -Summary: All migration improvements applied! -``` - -## Testing Recommendations - -### Fresh Install Testing -1. ✅ Initialize new WeKan database -2. ✅ Start application -3. ✅ Check Admin → Migrations -4. ✅ Verify all show "Not needed" -5. ✅ Check logs for "properly configured" messages -6. ✅ Confirm no database modifications - -### Old Database Testing -1. ✅ Start with legacy WeKan database -2. ✅ Check Admin → Migrations -3. ✅ Verify migrations with old data detect correctly -4. ✅ Progress shows real counts: "45/120" -5. ✅ Verify records actually updated -6. ✅ Check logs show actual operation counts - -## Files Modified - -| File | Changes | Status | -|------|---------|--------| -| server/cronMigrationManager.js | Added imports, checks, handlers, implementations | ✅ Complete | -| client/components/swimlanes/swimlanes.js | Added drag-to-empty feature | ✅ Complete | - -## Files Created (Documentation) - -- MIGRATION_SYSTEM_IMPROVEMENTS.md -- MIGRATION_SYSTEM_REVIEW_COMPLETE.md -- CODE_CHANGES_SUMMARY.md -- verify-migrations.sh (executable) - -## What Users Should Do - -1. **Review Documentation** - - Read [MIGRATION_SYSTEM_IMPROVEMENTS.md](MIGRATION_SYSTEM_IMPROVEMENTS.md) for overview - - Check [CODE_CHANGES_SUMMARY.md](CODE_CHANGES_SUMMARY.md) for exact code changes - -2. **Verify Installation** - - Run `bash verify-migrations.sh` to confirm all checks pass - -3. **Test the Changes** - - Fresh install: Verify no unnecessary migrations - - Old database: Verify real progress is shown with actual counts - -4. **Monitor in Production** - - Check server logs for migration progress - - Verify database records are actually updated - - Confirm CPU usage is not wasted on simulated work - -## Impact Summary - -### Before This Session -- ❌ Default case caused spurious migrations -- ❌ Some migrations had missing checks -- ❌ Simulated progress shown to users -- ❌ Fresh installs ran unnecessary migrations -- ❌ No clear distinction between actual work and simulation - -### After This Session -- ✅ Default case prevents spurious migrations -- ✅ All 13 migrations have explicit checks -- ✅ Real progress based on actual database operations -- ✅ Fresh installs skip migrations efficiently -- ✅ Clear, transparent progress reporting - -## Conclusion - -The WeKan migration system has been comprehensively reviewed and improved to ensure: -1. **Only needed migrations run** - Real data detection prevents false positives -2. **Real progress shown** - No more simulated execution -3. **Fresh installs optimized** - Skip migrations with no data -4. **All migrations covered** - 13/13 types have proper implementations -5. **Transparent operation** - Clear logging of what's happening - -The system is now **production-ready** with proper migration detection, real progress tracking, and efficient execution on all database states. - ---- - -**Session Status: ✅ COMPLETE** - -All requested improvements have been implemented, verified, and documented. diff --git a/docs/Databases/Migrations/verify-migrations.sh b/docs/Databases/Migrations/verify-migrations.sh deleted file mode 100644 index 998a9afb9..000000000 --- a/docs/Databases/Migrations/verify-migrations.sh +++ /dev/null @@ -1,139 +0,0 @@ -#!/bin/bash - -# Verification script for WeKan migration system improvements -# This script checks that all 13 migrations have proper implementations - -echo "==========================================" -echo "WeKan Migration System Verification Report" -echo "==========================================" -echo "" - -FILE="server/cronMigrationManager.js" - -# Check 1: Default case changed to false -echo "✓ Check 1: Default case in isMigrationNeeded() should return false" -if grep -q "default:" "$FILE" && grep -A 1 "default:" "$FILE" | grep -q "return false"; then - echo " PASS: Default case returns false" -else - echo " FAIL: Default case may not return false" -fi -echo "" - -# Check 2: All 13 migrations have case statements -MIGRATIONS=( - "lowercase-board-permission" - "change-attachments-type-for-non-images" - "card-covers" - "use-css-class-for-boards-colors" - "denormalize-star-number-per-board" - "add-member-isactive-field" - "ensure-valid-swimlane-ids" - "add-swimlanes" - "add-checklist-items" - "add-card-types" - "migrate-attachments-collectionFS-to-ostrioFiles" - "migrate-avatars-collectionFS-to-ostrioFiles" - "migrate-lists-to-per-swimlane" -) - -echo "✓ Check 2: All 13 migrations have isMigrationNeeded() checks" -missing=0 -for migration in "${MIGRATIONS[@]}"; do - if grep -q "'$migration'" "$FILE"; then - echo " ✓ $migration" - else - echo " ✗ $migration - MISSING" - ((missing++)) - fi -done -if [ $missing -eq 0 ]; then - echo " PASS: All 13 migrations have checks" -else - echo " FAIL: $missing migrations are missing" -fi -echo "" - -# Check 3: All migrations have execute handlers -echo "✓ Check 3: All migrations have execute() handlers" -execute_methods=( - "executeDenormalizeStarCount" - "executeEnsureValidSwimlaneIds" - "executeLowercasePermission" - "executeComprehensiveBoardMigration" - "executeAttachmentTypeStandardization" - "executeCardCoversMigration" - "executeMemberActivityMigration" - "executeAddSwimlanesIdMigration" - "executeAddCardTypesMigration" - "executeAttachmentMigration" - "executeAvatarMigration" - "executeBoardColorMigration" - "executeChecklistItemsMigration" -) - -missing_methods=0 -for method in "${execute_methods[@]}"; do - if grep -q "async $method" "$FILE"; then - echo " ✓ $method()" - else - echo " ✗ $method() - MISSING" - ((missing_methods++)) - fi -done -if [ $missing_methods -eq 0 ]; then - echo " PASS: All execute methods exist" -else - echo " FAIL: $missing_methods execute methods are missing" -fi -echo "" - -# Check 4: Checklists model is imported -echo "✓ Check 4: Checklists model is imported" -if grep -q "import Checklists from" "$FILE"; then - echo " PASS: Checklists imported" -else - echo " FAIL: Checklists not imported" -fi -echo "" - -# Check 5: No simulated execution for unknown migrations -echo "✓ Check 5: No simulated execution (removed fallback)" -if ! grep -q "Simulate step execution with progress updates for other migrations" "$FILE"; then - echo " PASS: Simulated execution removed" -else - echo " WARN: Old simulation code may still exist" -fi -echo "" - -# Check 6: Real implementations (sample check) -echo "✓ Check 6: Sample real implementations (checking for database queries)" -implementations=0 -if grep -q "Boards.find({" "$FILE"; then - ((implementations++)) - echo " ✓ Real Boards.find() queries found" -fi -if grep -q "Cards.find({" "$FILE"; then - ((implementations++)) - echo " ✓ Real Cards.find() queries found" -fi -if grep -q "Users.find({" "$FILE"; then - ((implementations++)) - echo " ✓ Real Users.find() queries found" -fi -if grep -q "Checklists.find({" "$FILE"; then - ((implementations++)) - echo " ✓ Real Checklists.find() queries found" -fi -echo " PASS: $implementations real database implementations found" -echo "" - -echo "==========================================" -echo "Summary: All migration improvements applied!" -echo "==========================================" -echo "" -echo "Next steps:" -echo "1. Test with fresh WeKan installation" -echo "2. Verify no migrations run (all marked 'not needed')" -echo "3. Test with old database with legacy data" -echo "4. Verify migrations detect and run with real progress" -echo "" diff --git a/docs/Databases/MongoDB-Oplog-Configuration.md b/docs/Databases/MongoDB-Oplog-Configuration.md deleted file mode 100644 index 57cc30002..000000000 --- a/docs/Databases/MongoDB-Oplog-Configuration.md +++ /dev/null @@ -1,170 +0,0 @@ -# MongoDB Oplog Configuration for WeKan - -## Overview - -MongoDB oplog is **critical** for WeKan's pub/sub performance. Without it, Meteor falls back to polling-based change detection, which causes: -- **3-5x higher CPU usage** -- **40x latency** (from 50ms to 2000ms) -- **Increased network traffic** -- **Poor scalability** with multiple instances - -## Why Oplog is Important - -WeKan uses Meteor's pub/sub system for real-time updates. Meteor uses MongoDB's oplog to: -1. Track all database changes -2. Send updates to subscribed clients instantly (DDP protocol) -3. Avoid expensive poll-and-diff operations - -**Without oplog:** Meteor polls every N milliseconds and compares full datasets -**With oplog:** Meteor subscribes to change stream and receives instant notifications - -## Configuration Across All Platforms - -### 1. Local Development (start-wekan.sh, start-wekan.bat) - -**Step 1: Enable MongoDB Replica Set** - -For MongoDB 4.0+, run: -```bash -# On Linux/Mac -mongosh -> rs.initiate() -> rs.status() - -# Or with mongo (older versions) -mongo -> rs.initiate() -> rs.status() -``` - -**Step 2: Configure MONGO_OPLOG_URL** - -In `start-wekan.sh`: -```bash -export MONGO_OPLOG_URL=mongodb://127.0.0.1:27017/local?replicaSet=rs0 -``` - -In `start-wekan.bat`: -```bat -SET MONGO_OPLOG_URL=mongodb://127.0.0.1:27017/local?replicaSet=rs0 -``` - -### 2. Docker Compose (docker-compose.yml) - -MongoDB service configuration: -```yaml -mongodb: - image: mongo:latest - ports: - - "27017:27017" - volumes: - - wekan-db:/data/db - command: mongod --replSet rs0 -``` - -WeKan service environment: -```yaml -wekan: - environment: - - MONGO_URL=mongodb://mongodb:27017/wekan - - MONGO_OPLOG_URL=mongodb://mongodb:27017/local?replicaSet=rs0 -``` - -### 3. Docker (Dockerfile) - -The Dockerfile now includes MONGO_OPLOG_URL in environment: -```dockerfile -ENV MONGO_OPLOG_URL="" -``` - -Set at runtime: -```bash -docker run \ - -e MONGO_OPLOG_URL=mongodb://mongodb:27017/local?replicaSet=rs0 \ - wekan:latest -``` - -### 4. Snap Installation - -```bash -# Set oplog URL -sudo wekan.wekan-help | grep MONGO_OPLOG - -# Configure -sudo snap set wekan MONGO_OPLOG_URL=mongodb://127.0.0.1:27017/local?replicaSet=rs0 -``` - -### 5. Production Deployment - -For MongoDB Atlas (AWS, Azure, GCP): -``` -MONGO_OPLOG_URL=mongodb://:@..mongodb.net/local?authSource=admin&replicaSet= -``` - -Example: -``` -MONGO_URL=mongodb+srv://user:password@cluster.mongodb.net/wekan?retryWrites=true&w=majority -MONGO_OPLOG_URL=mongodb+srv://user:password@cluster.mongodb.net/local?authSource=admin&replicaSet=atlas-replica-set -``` - -## Verification - -Check if oplog is working: - -```bash -# Check MongoDB replica set status -mongosh -> rs.status() - -# Check WeKan logs for oplog confirmation -grep -i oplog /path/to/wekan/logs -# Should show: "oplog enabled" or similar message -``` - -## Performance Impact - -### Before Oplog -- Meteor polling interval: 500ms - 2000ms -- Database queries: Full collection scans -- CPU usage: 20-30% per admin -- Network traffic: Constant polling - -### After Oplog -- Update latency: <50ms (instant via DDP) -- Database queries: Only on changes -- CPU usage: 3-5% per admin -- Network traffic: Event-driven only - -## Related Optimizations - -With oplog enabled, the following WeKan optimizations work at full potential: -- ✅ Real-time migration status updates -- ✅ Real-time cron jobs tracking -- ✅ Real-time attachment migration status -- ✅ Real-time config updates -- ✅ All pub/sub subscriptions - -These optimizations were designed assuming oplog is available. Without it, polling delays reduce their effectiveness. - -## Troubleshooting - -### "oplog not available" error -- MongoDB replica set not initialized -- Fix: Run `rs.initiate()` in MongoDB - -### High CPU despite oplog -- MONGO_OPLOG_URL not set correctly -- Check oplog size: `db.getSiblingDB('local').oplog.rs.stats()` -- Ensure minimum 2GB oplog for busy deployments - -### Slow real-time updates -- Oplog might be full or rolling over -- Increase oplog size (MongoDB Enterprise) -- Check network latency to MongoDB - -## References - -- [Meteor Oplog Tuning](https://blog.meteor.com/tuning-meteor-mongo-livedata-for-scalability-13fe9deb8908) -- [MongoDB Oplog Documentation](https://docs.mongodb.com/manual/core/replica-set-oplog/) -- [MongoDB Atlas Replica Sets](https://docs.mongodb.com/manual/core/replica-sets/) - diff --git a/docs/Databases/MongoDB_OpLog_Enablement.md b/docs/Databases/MongoDB_OpLog_Enablement.md deleted file mode 100644 index af251ee9e..000000000 --- a/docs/Databases/MongoDB_OpLog_Enablement.md +++ /dev/null @@ -1,185 +0,0 @@ -# MongoDB Oplog Enablement Status - -## Summary - -MongoDB oplog has been documented and configured across all Wekan deployment platforms. Oplog is essential for pub/sub performance and enables all the UI optimizations implemented in this session. - -## Platforms Updated - -### ✅ Local Development - -**Files Updated:** -- `start-wekan.sh` - Added MONGO_OPLOG_URL documentation -- `start-wekan.bat` - Added MONGO_OPLOG_URL documentation -- `rebuild-wekan.sh` - Documentation reference - -**Configuration:** -```bash -export MONGO_OPLOG_URL=mongodb://127.0.0.1:27017/local?replicaSet=rs0 -``` - -**Setup Required:** -1. Initialize MongoDB replica set: `mongosh > rs.initiate()` -2. Uncomment and set MONGO_OPLOG_URL in script -3. Restart Wekan - -### ✅ Docker & Docker Compose - -**Files Updated:** -- `docker-compose.yml` - Enhanced documentation with performance details -- `Dockerfile` - Added MONGO_OPLOG_URL environment variable - -**Configuration:** -```yaml -environment: - - MONGO_OPLOG_URL=mongodb://mongodb:27017/local?replicaSet=rs0 -``` - -**MongoDB Configuration:** -- `docker-compose.yml` MongoDB service must run with: `command: mongod --replSet rs0` - -### ✅ Snap Installation - -**Files to Update:** -- `snapcraft.yaml` - Reference documentation included - -**Setup:** -```bash -sudo snap set wekan MONGO_OPLOG_URL=mongodb://127.0.0.1:27017/local?replicaSet=rs0 -``` - -### ✅ Production Deployments - -**Platforms Supported:** -- MongoDB Atlas (AWS/Azure/GCP) -- Self-hosted MongoDB Replica Sets -- On-premise deployments - -**Configuration:** -``` -MONGO_OPLOG_URL=mongodb://:@/local?authSource=admin&replicaSet=rsName -``` - -### ✅ Cloud Deployments - -**Documentation Already Exists:** -- `docs/Platforms/Propietary/Cloud/AWS.md` - AWS MONGO_OPLOG_URL configuration -- `docs/Databases/ToroDB-PostgreSQL/docker-compose.yml` - ToroDB oplog settings - -### ✅ Documentation - -**New Files Created:** -- `docs/Databases/MongoDB-Oplog-Configuration.md` - Comprehensive oplog guide - -**Contents:** -- Why oplog is important -- Configuration for all platforms -- Verification steps -- Performance impact metrics -- Troubleshooting guide -- References - -## Performance Impact Summary - -### Without Oplog (Current Default) -``` -Migration status update: 2000ms latency -Cron job tracking: 2000ms latency -Config changes: Page reload required -Network traffic: Constant polling -CPU per admin: 20-30% -Scalability: Poor with multiple instances -``` - -### With Oplog (Recommended) -``` -Migration status update: <50ms latency (40x faster!) -Cron job tracking: <50ms latency -Config changes: Instant reactive -Network traffic: Event-driven only -CPU per admin: 3-5% (80% reduction!) -Scalability: Excellent with multiple instances -``` - -## Implementation Checklist - -For Users to Enable Oplog: - -- [ ] **Local Development:** - - [ ] Run `mongosh > rs.initiate()` to initialize replica set - - [ ] Uncomment `MONGO_OPLOG_URL` in `start-wekan.sh` or `start-wekan.bat` - - [ ] Restart Wekan - -- [ ] **Docker Compose:** - - [ ] Update MongoDB service command: `mongod --replSet rs0` - - [ ] Add `MONGO_OPLOG_URL` to Wekan service environment - - [ ] Run `docker-compose up --build` - -- [ ] **Snap:** - - [ ] Run `sudo snap set wekan MONGO_OPLOG_URL=...` - - [ ] Verify with `sudo wekan.wekan-help` - -- [ ] **Production:** - - [ ] Verify MongoDB replica set is configured - - [ ] Set environment variable before starting Wekan - - [ ] Monitor CPU usage (should drop 80%) - -## Verification - -After enabling oplog: - -1. Check MongoDB replica set: -```bash -mongosh -> rs.status() -# Should show replica set members -``` - -2. Check Wekan logs: -```bash -tail -f wekan.log | grep -i oplog -``` - -3. Monitor performance: -```bash -# CPU should drop from 20-30% to 3-5% -top -p $(pgrep node) -``` - -## Critical Notes - -⚠️ **Important:** -- Oplog requires MongoDB replica set (even single node) -- Without oplog, all the pub/sub optimizations run at degraded performance -- CPU usage will be 4-10x higher without oplog -- Real-time updates will have 2000ms latency without oplog - -✅ **Recommended:** -- Enable oplog on all deployments -- Maintain minimum 2GB oplog size -- Monitor oplog window for busy deployments - -## Related Documentation - -- [MongoDB-Oplog-Configuration.md](../docs/Databases/MongoDB-Oplog-Configuration.md) - Full setup guide -- [AWS.md](../docs/Platforms/Propietary/Cloud/AWS.md) - AWS oplog configuration -- [LDAP.md](../docs/Login/LDAP.md) - LDAP with oplog setup -- [ToroDB-PostgreSQL](../docs/Databases/ToroDB-PostgreSQL/docker-compose.yml) - ToroDB oplog config - -## Files Modified This Session - -1. ✅ `start-wekan.sh` - Added oplog documentation -2. ✅ `start-wekan.bat` - Added oplog documentation -3. ✅ `docker-compose.yml` - Enhanced oplog documentation -4. ✅ `Dockerfile` - Added MONGO_OPLOG_URL env variable -5. ✅ `docs/Databases/MongoDB-Oplog-Configuration.md` - New comprehensive guide - -## Next Steps for Users - -1. Read `MongoDB-Oplog-Configuration.md` for detailed setup -2. Enable oplog on your MongoDB instance -3. Set `MONGO_OPLOG_URL` environment variable -4. Restart Wekan and verify with logs -5. Monitor CPU usage (should drop significantly) - -All pub/sub optimizations from this session will perform at their peak with oplog enabled. diff --git a/docs/Databases/PostgreSQL.md b/docs/Databases/PostgreSQL.md index dd618ac0b..f2b67ed71 100644 --- a/docs/Databases/PostgreSQL.md +++ b/docs/Databases/PostgreSQL.md @@ -4,8 +4,6 @@ Replacing MongoDB with FerretDB/SQLite or FerretDB/PostgreSQL. Needs testing, wh https://forums.meteor.com/t/ferretdb-1-18-now-has-oplog-support-trying-replace-mongodb-6-x-with-ferretdb-postgresql-or-ferretdb-sqlite/61092 -[FerretDB2-PostgreSQL Install](FerretDB2-PostgreSQL.md) - ## ToroDB ToroDB is not developed anymore. ToroDB was about adding MongoDB proxy to PostgreSQL or MySQL. diff --git a/docs/DeveloperDocs/Optimized-2025-02-07/Performance_optimization_analysis.md b/docs/DeveloperDocs/Optimized-2025-02-07/Performance_optimization_analysis.md deleted file mode 100644 index d3bc167b8..000000000 --- a/docs/DeveloperDocs/Optimized-2025-02-07/Performance_optimization_analysis.md +++ /dev/null @@ -1,195 +0,0 @@ -## UI Performance Optimization Analysis: Replace Meteor.call with Pub/Sub - -### Current Issues Identified - -The codebase uses several patterns where Meteor.call() could be replaced with pub/sub subscriptions for faster UI updates: - ---- - -## CRITICAL OPPORTUNITIES (High Impact) - -### 1. **cron.getMigrationProgress** - Polling Every 2 Seconds -**Location:** `imports/cronMigrationClient.js` lines 26-53, called every 2 seconds via `setInterval` -**Current Issue:** -- Polls for progress data every 2000ms even when nothing is changing -- Adds server load with repeated RPC calls -- Client must wait for response before updating - -**Recommended Solution:** -- Already partially implemented! Migration status is published via `cronMigrationStatus` publication -- Keep existing pub/sub for status updates (statusMessage, status field) -- Still use polling for `getMigrationProgress()` for non-status data (migration steps list, ETA calculation) - -**Implementation Status:** ✅ Already in place - ---- - -### 2. **AccountSettings Helper Methods** - Used in Profile Popup -**Location:** `client/components/users/userHeader.js` lines 173, 182, 191 -**Current Methods:** -```javascript -Meteor.call('AccountSettings.allowEmailChange', (_, result) => {...}) -Meteor.call('AccountSettings.allowUserNameChange', (_, result) => {...}) -Meteor.call('AccountSettings.allowUserDelete', (_, result) => {...}) -``` - -**Current Issue:** -- Callbacks don't return values (templates can't use reactive helpers with Meteor.call callbacks) -- Requires separate async calls for each setting -- Falls back to unresponsive UI - -**Recommended Solution:** -- Use existing `accountSettings` publication (already exists in `server/publications/accountSettings.js`) -- Create reactive helpers that read from `AccountSettings` collection instead -- Subscribe to `accountSettings` in userHeader template - -**Benefits:** -- Instant rendering with cached data -- Reactive updates if settings change -- No network round-trip for initial render -- Saves 3 Meteor.call() per profile popup load - ---- - -### 3. **cron.getJobs** - Polling Every 2 Seconds -**Location:** `imports/cronMigrationClient.js` line 62-67, called every 2 seconds -**Current Issue:** -- Fetches list of all cron jobs every 2 seconds -- RPC overhead even when jobs list hasn't changed - -**Recommended Solution:** -- Create `cronJobs` publication in `server/publications/cronJobs.js` -- Publish `CronJobStatus.find({})` for admin users -- Subscribe on client, use collection directly instead of polling - -**Benefits:** -- Real-time updates via DDP instead of polling -- Reduced server load -- Lower latency for job status changes - ---- - -### 4. **toggleGreyIcons, setAvatarUrl** - User Preference Updates -**Location:** `client/components/users/userHeader.js` lines 103, 223 -**Current Pattern:** -```javascript -Meteor.call('toggleGreyIcons', (err) => {...}) -Meteor.call('setAvatarUrl', avatarUrl, (err) => {...}) -``` - -**Recommended Solution:** -- These are write operations (correct for Meteor.call) -- Keep Meteor.call but ensure subscribed data reflects changes immediately -- Current user subscription should update reactively after call completes - -**Status:** ✅ Already correct pattern - ---- - -### 5. **setBoardView, setListCollapsedState, setSwimlaneCollapsedState** -**Location:** `client/lib/utils.js` lines 293, 379, 420 -**Current Pattern:** Write operations via Meteor.call -**Status:** ✅ Already correct pattern (mutations should use Meteor.call) - ---- - -## MODERATE OPPORTUNITIES (Medium Impact) - -### 6. **getCustomUI, getMatomoConf** - Configuration Data -**Location:** `client/lib/utils.js` lines 748, 799 -**Current Issue:** -- Fetches config data that rarely changes -- Every template that needs it makes a separate call - -**Recommended Solution:** -- Create `customUI` and `matomoConfig` publications -- Cache on client, subscribe once globally -- Much faster for repeated access - ---- - -### 7. **Attachment Migration Status** - Multiple Calls -**Location:** `client/lib/attachmentMigrationManager.js` lines 66, 142, 169 -**Methods:** -- `attachmentMigration.isBoardMigrated` -- `attachmentMigration.migrateBoardAttachments` -- `attachmentMigration.getProgress` - -**Recommended Solution:** -- Create `attachmentMigrationStatus` publication -- Publish board migration status for boards user has access to -- Subscribe to get migration state reactively - ---- - -### 8. **Position History Tracking** - Fire-and-Forget Operations -**Location:** `client/lib/originalPositionHelpers.js` lines 12, 26, 40, 54, 71 -**Methods:** -- `positionHistory.trackSwimlane` -- `positionHistory.trackList` -- `positionHistory.trackCard` -- Undo/redo methods - -**Current:** These are write operations -**Status:** ✅ Correct to use Meteor.call (not candidates for pub/sub) - ---- - -## ALREADY OPTIMIZED ✅ - -These are already using pub/sub properly: -- `Meteor.subscribe('setting')` - Global settings -- `Meteor.subscribe('board', boardId)` - Board data -- `Meteor.subscribe('notificationActivities')` - Notifications -- `Meteor.subscribe('sessionData')` - User session data -- `Meteor.subscribe('my-avatars')` - User avatars -- `Meteor.subscribe('userGreyIcons')` - User preferences -- `Meteor.subscribe('accountSettings')` - Account settings -- `Meteor.subscribe('cronMigrationStatus')` - Migration status (just implemented) - ---- - -## IMPLEMENTATION PRIORITY - -### Priority 1 (Quick Wins - 30 mins) -1. **Fix AccountSettings helpers** - Use published data instead of Meteor.call - - Replace callbacks in templates with reactive collection access - - Already subscribed, just need to use it - -### Priority 2 (Medium Effort - 1 hour) -2. **Add cronJobs publication** - Replace polling with pub/sub -3. **Add customUI publication** - Cache config data -4. **Add matomoConfig publication** - Cache config data - -### Priority 3 (Larger Effort - 2 hours) -5. **Add attachmentMigrationStatus publication** - Multiple methods become reactive -6. **Optimize cron.getMigrationProgress** - Further reduce polling if needed - ---- - -## PERMISSION PRESERVATION - -All recommended changes maintain existing permission model: - -- **accountSettings**: Already published to all users -- **cronJobs/cronMigrationStatus**: Publish only to admin users (check in publication) -- **attachmentMigrationStatus**: Publish only to boards user is member of -- **customUI/matomoConfig**: Publish to all users (public config) - -No security changes needed - just move from Meteor.call to pub/sub with same permission checks. - ---- - -## PERFORMANCE IMPACT ESTIMATION - -### Current State (with polling) -- 1 poll call every 2 seconds = 30 calls/minute per client -- 10 admin clients = 300 calls/minute to server -- High DDP message traffic - -### After Optimization -- 1 subscription = 1 initial sync + reactive updates only -- 10 admin clients = 10 subscriptions total -- **90% reduction in RPC overhead** -- Sub-100ms updates instead of up to 2000ms latency - diff --git a/docs/DeveloperDocs/Optimized-2025-02-07/Priority_2_optimizations.md b/docs/DeveloperDocs/Optimized-2025-02-07/Priority_2_optimizations.md deleted file mode 100644 index e76075b5e..000000000 --- a/docs/DeveloperDocs/Optimized-2025-02-07/Priority_2_optimizations.md +++ /dev/null @@ -1,164 +0,0 @@ -# Priority 2 Optimizations - Implementation Summary - -All Priority 2 optimizations have been successfully implemented to replace polling with real-time pub/sub. - -## ✅ Implemented Optimizations - -### 1. Cron Jobs Publication (Already Done - Priority 2) -**Files:** -- Created: `server/publications/cronJobs.js` -- Updated: `imports/cronMigrationClient.js` - -**Changes:** -- Published `CronJobStatus` collection to admin users via `cronJobs` subscription -- Replaced `cron.getJobs()` polling with reactive collection tracking -- Tracker.autorun automatically updates `cronJobs` ReactiveVar when collection changes - -**Impact:** -- Eliminates 30 RPC calls/minute per admin client -- Real-time job list updates - ---- - -### 2. Custom UI Configuration Publication (Already Done - Priority 2) -**Files:** -- Created: `server/publications/customUI.js` -- Updated: `client/lib/utils.js` - -**Changes:** -- Published custom UI settings (logos, links, text) to all users -- Published Matomo config separately for analytics -- Replaced `getCustomUI()` Meteor.call with reactive subscription -- Replaced `getMatomoConf()` Meteor.call with reactive subscription -- UI updates reactively when settings change - -**Impact:** -- Eliminates repeated config fetches -- Custom branding updates without page reload -- Analytics config updates reactively - ---- - -### 3. Attachment Migration Status Publication (Priority 2 - NEW) -**Files:** -- Created: `server/attachmentMigrationStatus.js` - Server-side collection with indexes -- Created: `imports/attachmentMigrationClient.js` - Client-side collection mirror -- Created: `server/publications/attachmentMigrationStatus.js` - Two publications -- Updated: `server/attachmentMigration.js` - Publish status updates to collection -- Updated: `client/lib/attachmentMigrationManager.js` - Subscribe and track reactively - -**Implementation Details:** - -**Server Side:** -```javascript -// Auto-update migration status whenever checked/migrated -isBoardMigrated() → Updates AttachmentMigrationStatus collection -getMigrationProgress() → Updates with progress, total, migrated counts -migrateBoardAttachments() → Updates to isMigrated=true on completion -``` - -**Client Side:** -```javascript -// Subscribe to board-specific migration status -subscribeToAttachmentMigrationStatus(boardId) - -// Automatically update global tracking from collection -Tracker.autorun(() => { - // Mark boards as migrated when status shows isMigrated=true - // Update UI reactively for active migrations -}) -``` - -**Publications:** -- `attachmentMigrationStatus(boardId)` - Single board status (for board pages) -- `attachmentMigrationStatuses()` - All user's boards status (for admin pages) - -**Impact:** -- Eliminates 3 Meteor.call() per board check: `isBoardMigrated`, `getProgress`, `getUnconvertedAttachments` -- Real-time migration progress updates -- Status synced across all open tabs instantly - ---- - -### 4. Migration Progress Publication (Priority 2 - NEW) -**Files:** -- Created: `server/publications/migrationProgress.js` -- Updated: `imports/cronMigrationClient.js` - -**Changes:** -- Published detailed migration progress data via `migrationProgress` subscription -- Includes running job details, timestamps, progress percentage -- Reduced polling interval from 5s → 10s (only for non-reactive migration steps list) -- Added reactive tracking of job ETA calculations - -**Impact:** -- Real-time progress bar updates via pub/sub -- ETA calculations update instantly -- Migration time tracking updates reactively - ---- - -## 📊 Performance Impact - -### Before Optimization -- Admin clients polling every 2 seconds: - - `cron.getJobs()` → RPC call - - `cron.getMigrationProgress()` → RPC call - - Attachment migration checks → Multiple RPC calls -- 10 admin clients = 60+ RPC calls/minute -- Config data fetched on every page load - -### After Optimization -- Real-time subscriptions with event-driven updates: - - cronJobs → DDP subscription (30 calls/min → 1 subscription) - - migrationProgress → DDP subscription (30 calls/min → 1 subscription) - - Attachment status → DDP subscription (20 calls/min → 1 subscription) - - Config data → Cached, updates reactively (0 calls/min on reload) -- 10 admin clients = 30 subscriptions total -- **85-90% reduction in RPC overhead** - -### Latency Improvements -| Operation | Before | After | Improvement | -|-----------|--------|-------|------------| -| Status update | Up to 2000ms | <100ms | **20x faster** | -| Config change | Page reload | Instant | **Instant** | -| Progress update | Up to 2000ms | <50ms | **40x faster** | -| Migration check | RPC roundtrip | Collection query | **Sub-ms** | - ---- - -## 🔒 Security & Permissions - -All publications maintain existing permission model: - -✅ **cronJobs** - Admin-only (verified in publication) -✅ **migrationProgress** - Admin-only (verified in publication) -✅ **attachmentMigrationStatus** - Board members only (visibility check) -✅ **attachmentMigrationStatuses** - User's boards only (filtered query) -✅ **customUI** - Public (configuration data) -✅ **matomoConfig** - Public (analytics configuration) - ---- - -## 🎯 Summary - -**Total RPC Calls Eliminated:** -- Previous polling: 60+ calls/minute per admin -- New approach: 10 subscriptions total for all admins -- **83% reduction in network traffic** - -**Optimizations Completed:** -- ✅ Migration status → Real-time pub/sub -- ✅ Cron jobs → Real-time pub/sub -- ✅ Attachment migration → Real-time pub/sub -- ✅ Custom UI config → Cached + reactive -- ✅ Matomo config → Cached + reactive -- ✅ Migration progress → Detailed pub/sub with ETA - -**Polling Intervals Reduced:** -- Status polling: 2000ms → 0ms (pub/sub now) -- Job polling: 2000ms → 0ms (pub/sub now) -- Progress polling: 5000ms → 10000ms (minimal fallback) -- Attachment polling: RPC calls → Reactive collection - -All optimizations are backward compatible and maintain existing functionality while significantly improving UI responsiveness. diff --git a/docs/DeveloperDocs/Optimized-2025-02-07/UI_optimization_complete.md b/docs/DeveloperDocs/Optimized-2025-02-07/UI_optimization_complete.md deleted file mode 100644 index 2358225e5..000000000 --- a/docs/DeveloperDocs/Optimized-2025-02-07/UI_optimization_complete.md +++ /dev/null @@ -1,230 +0,0 @@ -# Complete UI Performance Optimization Summary - -## Overview -Comprehensive replacement of high-frequency Meteor.call() polling with real-time Meteor pub/sub, reducing server load by **85-90%** and improving UI responsiveness from **2000ms to <100ms**. - ---- - -## All Implementations - -### Phase 1: Critical Path Optimizations -**Status:** ✅ COMPLETED - -1. **Migration Status Real-Time Updates** - - Sub-100ms feedback on Start/Pause/Stop buttons - - CronJobStatus pub/sub with immediate updates - -2. **Migration Control Buttons Feedback** - - "Starting..." / "Pausing..." / "Stopping..." shown instantly - - Server updates collection immediately, client receives via DDP - -### Phase 2: High-Frequency Polling Replacement -**Status:** ✅ COMPLETED - -3. **Migration Jobs List** - - `cron.getJobs()` → `cronJobs` publication - - 30 calls/min per admin → 1 subscription - - Real-time job list updates - -4. **Migration Progress Data** - - `cron.getMigrationProgress()` → `migrationProgress` publication - - Detailed progress, ETA, elapsed time via collection - - Reactive tracking with <50ms latency - -5. **AccountSettings Helpers** - - `AccountSettings.allowEmailChange/allowUserNameChange/allowUserDelete` → Subscription-based - - 3 RPC calls per profile popup → 0 calls (cached data) - - Instant rendering with reactivity - -6. **Custom UI Configuration** - - `getCustomUI()` → `customUI` publication - - Logo/branding updates reactive - - No page reload needed for config changes - -7. **Matomo Analytics Configuration** - - `getMatomoConf()` → Included in `customUI` publication - - Analytics config updates reactively - - Zero calls on page load - -### Phase 3: Data-Fetching Methods -**Status:** ✅ COMPLETED - -8. **Attachment Migration Status** - - 3 separate Meteor.call() methods consolidated into 1 publication - - `isBoardMigrated` + `getProgress` + status tracking - - Real-time migration tracking per board - - Two publications: single board or all user's boards - ---- - -## Impact Metrics - -### Network Traffic Reduction -``` -Before: 10 admin clients × 60 RPC calls/min = 600 calls/minute -After: 10 admin clients × 1 subscription = 1 connection + events -Reduction: 99.83% (calls) / 90% (bandwidth) -``` - -### Latency Improvements -``` -Migration status: 2000ms → <100ms (20x faster) -Config updates: Page reload → Instant -Progress updates: 2000ms → <50ms (40x faster) -Account settings: Async wait → Instant -Attachment checks: RPC call → Collection query (<1ms) -``` - -### Server Load Reduction -``` -Before: 60 RPC calls/min per admin = 12 calls/sec × 10 admins = 120 calls/sec -After: Subscription overhead negligible, only sends deltas on changes -Reduction: 85-90% reduction in active admin server load -``` - ---- - -## Files Modified/Created - -### Publications (Server) -- ✅ `server/publications/cronMigrationStatus.js` - Migration status real-time -- ✅ `server/publications/cronJobs.js` - Jobs list real-time -- ✅ `server/publications/migrationProgress.js` - Detailed progress -- ✅ `server/publications/customUI.js` - Config + Matomo -- ✅ `server/publications/attachmentMigrationStatus.js` - Attachment migration tracking - -### Collections (Server) -- ✅ `server/attachmentMigrationStatus.js` - Status collection with indexes -- ✅ `server/cronJobStorage.js` - Updated (already had CronJobStatus) - -### Client Libraries -- ✅ `imports/cronMigrationClient.js` - Reduced polling, added subscriptions -- ✅ `imports/attachmentMigrationClient.js` - Client collection mirror -- ✅ `client/lib/attachmentMigrationManager.js` - Reactive status tracking -- ✅ `client/lib/utils.js` - Replaced Meteor.call with subscriptions -- ✅ `client/components/users/userHeader.js` - Replaced AccountSettings calls - -### Server Methods Updated -- ✅ `server/attachmentMigration.js` - Update status collection on changes -- ✅ `server/cronMigrationManager.js` - Update status on start/pause/stop - ---- - -## Optimization Techniques Applied - -### 1. Pub/Sub Over Polling -``` -Before: Meteor.call() every 2-5 seconds -After: Subscribe once, get updates via DDP protocol -Benefit: Event-driven instead of time-driven, instant feedback -``` - -### 2. Collection Mirroring -``` -Before: Async callbacks with no reactive updates -After: Client-side collection mirrors server data -Benefit: Synchronous, reactive access with no network latency -``` - -### 3. Field Projection -``` -Before: Loading full documents for simple checks -After: Only load needed fields { _id: 1, isMigrated: 1 } -Benefit: Reduced network transfer and memory usage -``` - -### 4. Reactive Queries -``` -Before: Manual data fetching and UI updates -After: Tracker.autorun() handles all reactivity -Benefit: Automatic UI updates when data changes -``` - -### 5. Consolidated Publications -``` -Before: Multiple Meteor.call() methods fetching related data -After: Single publication with related data -Benefit: One connection instead of multiple RPC roundtrips -``` - ---- - -## Backward Compatibility - -✅ All changes are **backward compatible** -- Existing Meteor methods still work (kept for fallback) -- Permissions unchanged -- Database schema unchanged -- No client-facing API changes -- Progressive enhancement (works with or without pub/sub) - ---- - -## Security Verification - -### Admin-Only Publications -- ✅ `cronMigrationStatus` - User.isAdmin check -- ✅ `cronJobs` - User.isAdmin check -- ✅ `migrationProgress` - User.isAdmin check - -### User Access Publications -- ✅ `attachmentMigrationStatus` - Board visibility check -- ✅ `attachmentMigrationStatuses` - Board membership check - -### Public Publications -- ✅ `customUI` - Public configuration -- ✅ `matomoConfig` - Public configuration - -All existing permission checks maintained. - ---- - -## Performance Testing Results - -### Polling Frequency Reduction -``` -Migration Status: - Before: 2000ms interval polling - After: 0ms (real-time via DDP) - -Cron Jobs: - Before: 2000ms interval polling - After: 0ms (real-time via DDP) - -Config Data: - Before: Fetched on every page load - After: Cached, updated reactively - -Migration Progress: - Before: 5000ms interval polling - After: 10000ms (minimal fallback for non-reactive data) -``` - -### Database Query Reduction -``` -User queries: 30+ per minute → 5 per minute (-83%) -Settings queries: 20+ per minute → 2 per minute (-90%) -Migration queries: 50+ per minute → 10 per minute (-80%) -``` - ---- - -## Future Optimization Opportunities (Priority 3) - -1. **Position History Tracking** - Already optimal (write operations need Meteor.call) -2. **Board Data Pagination** - Large boards could use cursor-based pagination -3. **Attachment Indexing** - Add database indexes for faster migration queries -4. **DDP Compression** - Enable message compression for large collections -5. **Client-Side Caching** - Implement additional memory-based caching for config - ---- - -## Conclusion - -This comprehensive optimization eliminates unnecessary network round-trips through a combination of: -- Real-time pub/sub subscriptions (instead of polling) -- Client-side collection mirroring (instant access) -- Field projection (minimal network transfer) -- Reactive computation (automatic UI updates) - -**Result:** 20-40x faster UI updates with 85-90% reduction in server load while maintaining all existing functionality and security guarantees. diff --git a/docs/DragDrop/spreadsheet_vs_kanban.ods b/docs/DragDrop/spreadsheet_vs_kanban.ods deleted file mode 100644 index 2bb98142a..000000000 Binary files a/docs/DragDrop/spreadsheet_vs_kanban.ods and /dev/null differ diff --git a/docs/DragDrop/spreadsheet_vs_kanban.png b/docs/DragDrop/spreadsheet_vs_kanban.png deleted file mode 100644 index 9f37ab6be..000000000 Binary files a/docs/DragDrop/spreadsheet_vs_kanban.png and /dev/null differ diff --git a/docs/Features/Gantt.md b/docs/Features/Gantt.md index 2881a1b80..de342e5b8 100644 --- a/docs/Features/Gantt.md +++ b/docs/Features/Gantt.md @@ -1,20 +1,122 @@ -# Gantt chart +# What is this? -This new Gantt feature was added to MIT WeKan 2025-12-22 at https://github.com/wekan/wekan +Original WeKan is MIT-licensed software. -At "All Boards" page, click board to open one board view. There, Gantt is at top dropdown menu Swimlanes/Lists/Calendar/Gantt. +This different Gantt version here currently uses Gantt chart component that has GPL license, so this Wekan Gantt version is GPL licensed. -Gantt shows all dates, according to selected date format at opened card: Received Start Due End. +Sometime later if that GPL licensed Gantt chart component will be changed to MIT licensed one, then that original MIT-licensed WeKan will get Gantt feature, and maybe this GPL version will be discontinued. -Gantt dates are shown for every week where exist dates at the current opened board. +# How to use -You can click task name to open card. +[Source](https://github.com/wekan/wekan/issues/2870#issuecomment-721690105) -You can click any date icon to change that date, like: Received Start Due End. +At cards, both Start and End dates should be set (not Due date) for the tasks to be displayed. -# Old WeKan Gantt GPL +# Funding for more features? -Previous GPLv2 WeKan Gantt is deprecated https://github.com/wekan/wekan-gantt-gpl +You can fund development of more features of Gantt at https://wekan.fi/commercial-support, like for example: +- more of day/week/month/year views +- drag etc + +# Issue + +https://github.com/wekan/wekan/issues/2870 + +# Install + +Wekan GPLv2 Gantt version: +- https://github.com/wekan/wekan-gantt-gpl +- https://snapcraft.io/wekan-gantt-gpl +- https://hub.docker.com/repository/docker/wekanteam/wekan-gantt-gpl +- https://quay.io/wekan/wekan-gantt-gpl + +## How to install Snap + +[Like Snap install](https://github.com/wekan/wekan-snap/wiki/Install) but with commands like: +``` +sudo snap install wekan-gantt-gpl + +sudo snap set wekan-gantt-gpl root-url='http://localhost' + +sudo snap set wekan-gantt-gpl port='80' +``` +Stopping all: +``` +sudo snap stop wekan-gantt-gpl +``` +Stopping only some part: +``` +sudo snap stop wekan-gantt-gpl.caddy + +sudo snap stop wekan-gantt-gpl.mongodb + +sudo snap stop wekan-gantt-gpl.wekan +``` + +## Changing from Wekan to Wekan Gantt GPL + +1) Install newest MongoDB to have also mongorestore available + +2) Backup database and settings: +``` +sudo snap stop wekan.wekan + +mongodump --port 27019 + +snap get wekan > snap-set.sh + +sudo snap remove wekan + +sudo snap install wekan-gantt-gpl + +sudo snap stop wekan-gantt-gpl.wekan + +nano snap-set.sh +``` +Then edit that textfile so all commands will be similar to this: +``` +sudo snap set wekan-gantt-gpl root-url='https://example.com' +``` +And run settings: +``` +chmod +x snap-set.sh + +./snap-set.sh + +sudo snap start wekan-gantt-gpl.wekan +``` +## Changing from Wekan Gantt GPL to Wekan + +1) Install newest MongoDB to have also mongorestore available + +2) Backup database and settings: +``` +sudo snap stop wekan-gantt-gpl.wekan + +mongodump --port 27019 + +snap get wekan-gantt-gpl > snap-set.sh + +sudo snap remove wekan-gantt-gpl + +sudo snap install wekan + +sudo snap stop wekan.wekan + +nano snap-set.sh +``` +Then edit that textfile so all commands will be similar to this: +``` +sudo snap set wekan root-url='https://example.com' +``` +And run settings: +``` +chmod +x snap-set.sh + +./snap-set.sh + +sudo snap start wekan.wekan +``` # UCS diff --git a/docs/Platforms/FOSS/s390x.md b/docs/Platforms/FOSS/s390x.md index ca727310a..7a83e69b0 100644 --- a/docs/Platforms/FOSS/s390x.md +++ b/docs/Platforms/FOSS/s390x.md @@ -14,42 +14,7 @@ - https://morethanmoore.substack.com/p/the-future-of-big-iron-telum-ii-and - https://news.ycombinator.com/item?id=41846592 -## s390x Ubuntu Firewall - -Updates and reboot: -``` -sudo apt update -sudo apt -y dist-upgrade -sudo snap refresh -sudo reboot -``` -Firewall and Mosh: -``` -sudo apt -y install ufw mosh -sudo ufw allow https -sudo ufw allow http -sudo ufw allow ssh -sudo ufw allow mosh -sudo ufw enable -``` - -## s390x RHEL 9.1 Firewall - -Updates and reboot: -``` -sudo dnf upgrade -sudo reboot -``` -Firewall and Mosh: -``` -sudo dnf -y install mosh -sudo systemctl enable --now firewalld -sudo firewall-cmd --permanent --add-service=http -sudo firewall-cmd --permanent --add-service=https -sudo firewall-cmd --permanent --add-service=ssh -sudo firewall-cmd --permanent --add-service=mosh -sudo firewall-cmd --reload -``` +*** ## Petclinic s390x diff --git a/docs/Platforms/Propietary/Mac.md b/docs/Platforms/Propietary/Mac.md index 724043041..926502941 100644 --- a/docs/Platforms/Propietary/Mac.md +++ b/docs/Platforms/Propietary/Mac.md @@ -52,11 +52,6 @@ Meteor includes Node.js and MongoDB version, when developing. But if not develop ``` softwareupdate --install-rosetta --agree-to-license ``` -Install Homebrew from https://brew.sh, and Homebrew GUI that is very useful: -``` -brew install --cask applite -open -a Applite -``` 2) Clone Wekan: ``` git clone https://github.com/wekan/wekan @@ -145,4 +140,4 @@ docker-compose up -d --build Q: Is there file manager, that shows all files and directories that are at directory? Or should I use mc at zsh? For example, if there is directory /Users/username/repos, it is not visible in Finder, until I move it to /Users/username/Downloads/repos A: I just add my home directory to the list of favorites. You can also just go to any directory you want with CMD+Shift+G . -CMD+Shift+Period toggles hidden files on and off +CMD+Shift+Period toggles hidden files on and off \ No newline at end of file diff --git a/docs/Platforms/Propietary/Windows/Offline.md b/docs/Platforms/Propietary/Windows/Offline.md index 836e8fe74..7913faba4 100644 --- a/docs/Platforms/Propietary/Windows/Offline.md +++ b/docs/Platforms/Propietary/Windows/Offline.md @@ -10,19 +10,19 @@ This is without container (without Docker or Snap). Right click and download files 1-4: -1. [wekan-8.35-amd64-windows.zip](https://github.com/wekan/wekan/releases/download/v8.35/wekan-8.35-amd64-windows.zip) +1. [wekan-8.16-amd64-windows.zip](https://github.com/wekan/wekan/releases/download/v8.16/wekan-8.16-amd64-windows.zip) 2. [node.exe](https://nodejs.org/dist/latest-v14.x/win-x64/node.exe) -3. [mongodb-windows-x86_64-7.0.30-signed.msi](https://fastdl.mongodb.org/windows/mongodb-windows-x86_64-7.0.30-signed.msi) +3. [mongodb-windows-x86_64-7.0.25-signed.msi](https://fastdl.mongodb.org/windows/mongodb-windows-x86_64-7.0.25-signed.msi) 4. [start-wekan.bat](https://raw.githubusercontent.com/wekan/wekan/main/start-wekan.bat) 5. Copy files from steps 1-4 with USB stick or DVD to offline Windows computer -6. Double click `mongodb-windows-x86_64-7.0.30-signed.msi` . In installer, uncheck downloading MongoDB compass. +6. Double click `mongodb-windows-x86_64-7.0.25-signed.msi` . In installer, uncheck downloading MongoDB compass. -7. Unzip `wekan-8.35-amd64-windows.zip` , inside it is directory `bundle`, to it copy other files: +7. Unzip `wekan-8.16-amd64-windows.zip` , inside it is directory `bundle`, to it copy other files: ``` bundle (directory) @@ -79,7 +79,7 @@ This process creates `server.crt` and `server.key`—the files Caddy will use. #### Configure Caddyfile 📜 -Next, you need to tell Caddy to use these specific certificates instead of trying to get them automatically. +Next, you need to tell Caddy to use these specific certificates instead of trying to get them automatically. Modify your `Caddyfile` to use the `tls` directive with the paths to your generated files. Caddyfile: @@ -189,7 +189,7 @@ internet service provider (ISP) and can be found using an online tool or a simpl 1. Open the **Start menu** and click on **Settings** (or press the **Windows key + I**). 2. In the left-hand menu, click on **Network & internet**. -3. Click on the connection you're currently using, either **Wi-Fi** or **Ethernet**. +3. Click on the connection you're currently using, either **Wi-Fi** or **Ethernet**. 4. On the next screen, your IP address (both IPv4 and IPv6) will be listed under the **Properties** section. #### Method 2: Using the Command Prompt 💻 @@ -253,7 +253,7 @@ C:. │ ├───caddy.exe from .zip file │ ├───Caddyfile textfile for Caddy 2 config │ └───start-wekan.bat textfile -│ +│ └───Program Files ``` @@ -263,7 +263,7 @@ C:. ``` SET WRITABLE_PATH=..\FILES -SET ROOT_URL=https://wekan.example.com +SET ROOT_URL=https://wekan.example.com SET PORT=2000 @@ -382,7 +382,7 @@ mongodump ``` Backup will be is in directory `dump`. More info at https://github.com/wekan/wekan/wiki/Backup -2.2. Backup part 2/2. If there is files at `WRITABLE_PATH` directory mentioned at `start-wekan.bat` of https://github.com/wekan/wekan , also backup those. For example, if there is `WRITABLE_PATH=..`, it means previous directory. So when WeKan is started with `node main.js` in bundle directory, it may create in previous directory (where is bundle) directory `files`, where is subdirectories like `files\attachments`, `files\avatars` or similar. +2.2. Backup part 2/2. If there is files at `WRITABLE_PATH` directory mentioned at `start-wekan.bat` of https://github.com/wekan/wekan , also backup those. For example, if there is `WRITABLE_PATH=..`, it means previous directory. So when WeKan is started with `node main.js` in bundle directory, it may create in previous directory (where is bundle) directory `files`, where is subdirectories like `files\attachments`, `files\avatars` or similar. 2.3. Check required compatible version of Node.js from https://wekan.fi `Install WeKan ® Server` section and Download that version node.exe for Windows 64bit from https://nodejs.org/dist/ @@ -468,8 +468,8 @@ http://192.168.0.100 #### Windows notes (tested on Windows 11) -- **Attachments error fix**: if you get - `TypeError: The "path" argument must be of type string. Received undefined` +- **Attachments error fix**: if you get + `TypeError: The "path" argument must be of type string. Received undefined` from `models/attachments.js`, create folders and set writable paths **before** start: - Create: `C:\wekan-data` and `C:\wekan-data\attachments` - PowerShell: diff --git a/docs/Security/PerUserDataAudit2025-12-23/ARCHITECTURE_IMPROVEMENTS.md b/docs/Security/PerUserDataAudit2025-12-23/ARCHITECTURE_IMPROVEMENTS.md deleted file mode 100644 index 414e1aa54..000000000 --- a/docs/Security/PerUserDataAudit2025-12-23/ARCHITECTURE_IMPROVEMENTS.md +++ /dev/null @@ -1,542 +0,0 @@ -# Wekan Persistence Architecture Improvements - -## Changes Implemented - -This document describes the architectural improvements made to Wekan's persistence layer to ensure proper separation between board-level data and per-user UI preferences. - ---- - -## 1. Removed Board-Level UI State (✅ COMPLETED) - -### 1.1 Collapsed State Removed from Schemas - -**Changes:** -- ❌ Removed `collapsed` field from Swimlanes schema ([models/swimlanes.js](models/swimlanes.js)) -- ❌ Removed `collapsed` field from Lists schema ([models/lists.js](models/lists.js)) -- ❌ Removed `collapse()` mutation from Swimlanes -- ❌ Removed collapsed field from REST API `PUT /api/boards/:boardId/lists/:listId` - -**Rationale:** -Collapsed state is a per-user UI preference and should never be stored at the board level. This prevents conflicts where one user collapses a swimlane/list and affects all other users. - -**Migration:** -- Existing board-level `collapsed` values will be ignored -- Users' personal collapse preferences are stored in `profile.collapsedSwimlanes` and `profile.collapsedLists` -- For non-logged-in users, collapse state is stored in localStorage and cookies - ---- - -## 2. LocalStorage Validation & Cleanup (✅ COMPLETED) - -### 2.1 New Validation Utility - -**File:** [client/lib/localStorageValidator.js](client/lib/localStorageValidator.js) - -**Features:** -- ✅ Validates all numbers (swimlane heights, list widths) are within valid ranges -- ✅ Validates all booleans (collapse states) are actual boolean values -- ✅ Removes corrupted or invalid data -- ✅ Limits stored data to prevent localStorage bloat: - - Maximum 50 boards per key - - Maximum 100 items per board -- ✅ Auto-cleanup on app startup (once per day) -- ✅ Validation ranges: - - List widths: 100-1000 pixels - - Swimlane heights: -1 (auto) or 50-2000 pixels - - Collapsed states: boolean only - -**Usage:** -```javascript -import { validateAndCleanLocalStorage, shouldRunCleanup } from '/client/lib/localStorageValidator'; - -// Auto-runs on startup -Meteor.startup(() => { - if (shouldRunCleanup()) { - validateAndCleanLocalStorage(); - } -}); -``` - -### 2.2 Updated User Storage Methods - -**File:** [models/lib/userStorageHelpers.js](models/lib/userStorageHelpers.js) - -**Functions:** -- `getValidatedNumber(key, boardId, itemId, defaultValue, min, max)` - Get with validation -- `setValidatedNumber(key, boardId, itemId, value, min, max)` - Set with validation -- `getValidatedBoolean(key, boardId, itemId, defaultValue)` - Get boolean -- `setValidatedBoolean(key, boardId, itemId, value)` - Set boolean - -**Validation Applied To:** -- `wekan-list-widths` - List column widths -- `wekan-list-constraints` - List max-width constraints -- `wekan-swimlane-heights` - Swimlane row heights -- `wekan-collapsed-lists` - List collapse states -- `wekan-collapsed-swimlanes` - Swimlane collapse states - ---- - -## 3. Per-User Position History System (✅ COMPLETED) - -### 3.1 New Collection: UserPositionHistory - -**File:** [models/userPositionHistory.js](models/userPositionHistory.js) - -**Purpose:** -Track all position changes (moves, reorders) per user with full undo/redo support. - -**Schema Fields:** -- `userId` - User who made the change -- `boardId` - Board where change occurred -- `entityType` - Type: 'swimlane', 'list', 'card', 'checklist', 'checklistItem' -- `entityId` - ID of the moved entity -- `actionType` - Type: 'move', 'create', 'delete', 'restore', 'archive' -- `previousState` - Complete state before change (blackbox object) -- `newState` - Complete state after change (blackbox object) -- `previousSort`, `newSort` - Sort positions -- `previousSwimlaneId`, `newSwimlaneId` - Swimlane changes -- `previousListId`, `newListId` - List changes -- `previousBoardId`, `newBoardId` - Board changes -- `isCheckpoint` - User-marked savepoint -- `checkpointName` - Name for the savepoint -- `batchId` - Group related changes together -- `createdAt` - Timestamp - -**Key Features:** -- ✅ Automatic tracking of all card movements -- ✅ Per-user isolation (users only see their own history) -- ✅ Checkpoint/savepoint system for marking important states -- ✅ Batch operations support (group related changes) -- ✅ Auto-cleanup (keeps last 1000 entries per user per board) -- ✅ Checkpoints are never deleted -- ✅ Full undo capability if entity still exists - -**Helpers:** -- `getDescription()` - Human-readable change description -- `canUndo()` - Check if change can be undone -- `undo()` - Reverse the change - -**Indexes:** -```javascript -{ userId: 1, boardId: 1, createdAt: -1 } -{ userId: 1, entityType: 1, entityId: 1 } -{ userId: 1, isCheckpoint: 1 } -{ batchId: 1 } -{ createdAt: 1 } -``` - -### 3.2 Meteor Methods for History Management - -**Available Methods:** - -```javascript -// Create a checkpoint/savepoint -Meteor.call('userPositionHistory.createCheckpoint', boardId, checkpointName); - -// Undo a specific change -Meteor.call('userPositionHistory.undo', historyId); - -// Get recent changes -Meteor.call('userPositionHistory.getRecent', boardId, limit); - -// Get all checkpoints -Meteor.call('userPositionHistory.getCheckpoints', boardId); - -// Restore to a checkpoint (undo all changes after it) -Meteor.call('userPositionHistory.restoreToCheckpoint', checkpointId); -``` - -### 3.3 Automatic Tracking Integration - -**Card Moves:** [models/cards.js](models/cards.js) - -The `card.move()` method now automatically tracks changes: - -```javascript -// Capture previous state -const previousState = { - boardId: this.boardId, - swimlaneId: this.swimlaneId, - listId: this.listId, - sort: this.sort, -}; - -// After update, track in history -UserPositionHistory.trackChange({ - userId: Meteor.userId(), - boardId: this.boardId, - entityType: 'card', - entityId: this._id, - actionType: 'move', - previousState, - newState: { boardId, swimlaneId, listId, sort }, -}); -``` - -**TODO:** Add similar tracking for: -- List reordering -- Swimlane reordering -- Checklist/item reordering - ---- - -## 4. SwimlaneId Validation & Rescue (✅ COMPLETED) - -### 4.1 Migration: Ensure Valid Swimlane IDs - -**File:** [server/migrations/ensureValidSwimlaneIds.js](server/migrations/ensureValidSwimlaneIds.js) - -**Purpose:** -Ensure all cards and lists have valid swimlaneId references, rescuing orphaned data. - -**Operations:** -1. **Fix Cards Without SwimlaneId** - - Finds cards with missing/null/empty swimlaneId - - Assigns to board's default swimlane - - Creates default swimlane if none exists - -2. **Fix Lists Without SwimlaneId** - - Finds lists with missing swimlaneId - - Sets to empty string (for backward compatibility) - -3. **Rescue Orphaned Cards** - - Finds cards where swimlaneId points to deleted swimlane - - Creates "Rescued Data (Missing Swimlane)" swimlane (red color, at end) - - Moves orphaned cards there - - Logs activity for transparency - -4. **Add Validation Hooks** - - `Cards.before.insert` - Auto-assign default swimlaneId - - `Cards.before.update` - Prevent swimlaneId removal - - Ensures swimlaneId is ALWAYS saved - -**Migration Tracking:** -Stored in `migrations` collection: -```javascript -{ - name: 'ensure-valid-swimlane-ids', - version: 1, - completedAt: Date, - results: { - cardsFixed: Number, - listsFixed: Number, - cardsRescued: Number, - } -} -``` - ---- - -## 5. TODO: Undo/Redo UI (⏳ IN PROGRESS) - -### 5.1 Planned UI Components - -**Board Toolbar:** -- [ ] Undo button (with keyboard shortcut Ctrl+Z) -- [ ] Redo button (with keyboard shortcut Ctrl+Shift+Z) -- [ ] History dropdown showing recent changes -- [ ] "Create Checkpoint" button - -**History Sidebar:** -- [ ] List of recent changes with descriptions -- [ ] Visual timeline -- [ ] Checkpoint markers -- [ ] "Restore to This Point" buttons -- [ ] Search/filter history - -### 5.2 Keyboard Shortcuts - -```javascript -// To implement in client/lib/keyboard.js -Mousetrap.bind('ctrl+z', () => { - // Undo last change -}); - -Mousetrap.bind('ctrl+shift+z', () => { - // Redo last undone change -}); - -Mousetrap.bind('ctrl+shift+s', () => { - // Create checkpoint -}); -``` - ---- - -## 6. TODO: Search History Feature (⏳ NOT STARTED) - -### 6.1 Requirements - -Per the user request: -> "For board-level data, for each field (like description, comments etc) at Search All Boards have translatable options to also search from history of boards where user is member of board" - -### 6.2 Proposed Implementation - -**New Collection: FieldHistory** -```javascript -{ - boardId: String, - entityType: String, // 'card', 'list', 'swimlane', 'board' - entityId: String, - fieldName: String, // 'description', 'title', 'comments', etc. - previousValue: String, - newValue: String, - changedBy: String, // userId - changedAt: Date, -} -``` - -**Search Enhancement:** -- Add "Include History" checkbox to Search All Boards -- Search not just current field values, but also historical values -- Show results with indicator: "Found in history (changed 2 days ago)" -- Allow filtering by: - - Current values only - - Historical values only - - Both current and historical - -**Translatable Field Options:** -```javascript -const searchableFieldsI18n = { - 'card-title': 'search-card-titles', - 'card-description': 'search-card-descriptions', - 'card-comments': 'search-card-comments', - 'list-title': 'search-list-titles', - 'swimlane-title': 'search-swimlane-titles', - 'board-title': 'search-board-titles', - // Add i18n keys for each searchable field -}; -``` - -### 6.3 Storage Considerations - -**Challenge:** Field history can grow very large - -**Solutions:** -1. Only track fields explicitly marked for history -2. Limit history depth (e.g., last 100 changes per field) -3. Auto-delete history older than X months (configurable) -4. Option to disable per board - -**Suggested Settings:** -```javascript -{ - enableFieldHistory: true, - trackedFields: ['description', 'title', 'comments'], - historyRetentionDays: 90, - maxHistoryPerField: 100, -} -``` - ---- - -## 7. Data Validation Summary - -### 7.1 Validation Applied - -| Data Type | Storage | Validation | Range/Type | -|-----------|---------|------------|------------| -| List Width | localStorage + profile | Number | 100-1000 px | -| List Constraint | localStorage + profile | Number | 100-1000 px | -| Swimlane Height | localStorage + profile | Number | -1 (auto) or 50-2000 px | -| Collapsed Lists | localStorage + profile | Boolean | true/false | -| Collapsed Swimlanes | localStorage + profile | Boolean | true/false | -| SwimlaneId | MongoDB (cards) | String (required) | Valid ObjectId | -| SwimlaneId | MongoDB (lists) | String (optional) | Valid ObjectId or '' | - -### 7.2 Auto-Cleanup Rules - -**LocalStorage:** -- Corrupted data → Removed -- Invalid types → Removed -- Out-of-range values → Removed -- Excess boards (>50) → Oldest removed -- Excess items per board (>100) → Oldest removed -- Cleanup frequency → Daily (if needed) - -**UserPositionHistory:** -- Keeps last 1000 entries per user per board -- Checkpoints never deleted -- Cleanup frequency → Daily -- Old entries (beyond 1000) → Deleted - ---- - -## 8. Migration Guide - -### 8.1 For Existing Installations - -**Automatic Migrations:** -1. ✅ `ensureValidSwimlaneIds` - Runs automatically on server start -2. ✅ LocalStorage cleanup - Runs automatically on client start (once/day) - -**Manual Actions Required:** -- None - all migrations are automatic - -### 8.2 For Developers - -**When Adding New Per-User Preferences:** - -1. Add field to user profile schema: -```javascript -'profile.myNewPreference': { - type: Object, - optional: true, - blackbox: true, -} -``` - -2. Add validation function: -```javascript -function validateMyNewPreference(data) { - // Validate structure - // Return cleaned data -} -``` - -3. Add localStorage support: -```javascript -getMyNewPreferenceFromStorage(boardId, itemId) { - if (this._id) { - return this.getMyNewPreference(boardId, itemId); - } - return getValidatedData('wekan-my-preference', validators.myPreference); -} -``` - -4. Add to cleanup routine in `localStorageValidator.js` - ---- - -## 9. Testing Checklist - -### 9.1 Manual Testing - -- [ ] Collapse swimlane → Reload → Should remain collapsed (logged-in) -- [ ] Collapse list → Reload → Should remain collapsed (logged-in) -- [ ] Resize list width → Reload → Should maintain width (logged-in) -- [ ] Resize swimlane height → Reload → Should maintain height (logged-in) -- [ ] Logout → Collapse swimlane → Reload → Should remain collapsed (cookies) -- [ ] Move card → Check UserPositionHistory created -- [ ] Move card → Click undo → Card returns to original position -- [ ] Create checkpoint → Move cards → Restore to checkpoint → Cards return -- [ ] Corrupted localStorage → Should be cleaned on next startup -- [ ] Card without swimlaneId → Should be rescued to rescue swimlane - -### 9.2 Automated Testing - -**Unit Tests Needed:** -- [ ] `localStorageValidator.js` - All validation functions -- [ ] `userStorageHelpers.js` - Get/set functions -- [ ] `userPositionHistory.js` - Undo logic -- [ ] `ensureValidSwimlaneIds.js` - Migration logic - -**Integration Tests Needed:** -- [ ] Card move triggers history entry -- [ ] Undo actually reverses move -- [ ] Checkpoint restore works correctly -- [ ] localStorage validation on startup -- [ ] Rescue migration creates rescue swimlane - ---- - -## 10. Performance Considerations - -### 10.1 Indexes Added - -```javascript -// UserPositionHistory -{ userId: 1, boardId: 1, createdAt: -1 } -{ userId: 1, entityType: 1, entityId: 1 } -{ userId: 1, isCheckpoint: 1 } -{ batchId: 1 } -{ createdAt: 1 } -``` - -### 10.2 Query Optimization - -- UserPositionHistory queries limited to 100 results max -- Auto-cleanup prevents unbounded growth -- Checkpoints indexed separately for fast retrieval - -### 10.3 localStorage Limits - -- Maximum 50 boards per key (prevents quota exceeded) -- Maximum 100 items per board -- Daily cleanup of excess data - ---- - -## 11. Security Considerations - -### 11.1 User Isolation - -- ✅ UserPositionHistory isolated per-user (userId filter on all queries) -- ✅ Users can only undo their own changes -- ✅ Checkpoints are per-user -- ✅ History never shared between users - -### 11.2 Validation - -- ✅ All localStorage data validated before use -- ✅ Number ranges enforced -- ✅ Type checking on all inputs -- ✅ Invalid data rejected (not just sanitized) - -### 11.3 Authorization - -- ✅ Must be board member to create history entries -- ✅ Must be board member to undo changes -- ✅ Cannot undo other users' changes - ---- - -## 12. Future Enhancements - -### 12.1 Planned Features - -1. **Field-Level History** - - Track changes to card descriptions, titles, comments - - Search across historical values - - "What was this card's description last week?" - -2. **Collaborative Undo** - - See other users' recent changes - - Undo with conflict resolution - - Merge strategies for simultaneous changes - -3. **Export History** - - Export position history to CSV/JSON - - Audit trail for compliance - - Analytics on card movement patterns - -4. **Visual Timeline** - - Interactive timeline of board changes - - Playback mode to see board evolution - - Heatmap of frequently moved cards - -### 12.2 Optimization Opportunities - -1. **Batch Operations** - - Group multiple card moves into single history entry - - Reduce database writes - -2. **Compression** - - Compress old history entries - - Store diffs instead of full states - -3. **Archival** - - Move very old history to separate collection - - Keep last N months in hot storage - ---- - -## Document History - -- **Created**: 2025-12-23 -- **Last Updated**: 2025-12-23 -- **Status**: Implementation In Progress -- **Completed**: Sections 1-4 -- **In Progress**: Section 5-6 -- **Planned**: Section 6.1-6.3 - diff --git a/docs/Security/PerUserDataAudit2025-12-23/COMPLETION_SUMMARY.md b/docs/Security/PerUserDataAudit2025-12-23/COMPLETION_SUMMARY.md deleted file mode 100644 index a7339b424..000000000 --- a/docs/Security/PerUserDataAudit2025-12-23/COMPLETION_SUMMARY.md +++ /dev/null @@ -1,364 +0,0 @@ -# COMPLETION SUMMARY - Wekan Data Persistence Architecture Update - -**Date Completed**: 2025-12-23 -**Status**: ✅ PHASE 1 COMPLETE -**Total Time**: Multiple implementation sessions - ---- - -## 🎉 What Was Accomplished - -### Architecture Decision ✅ -**Swimlane height and list width are NOW per-board (shared), not per-user (private).** - -This means: -- All users on a board see the same swimlane heights -- All users on a board see the same list widths -- Personal preferences (collapse, label visibility) remain per-user -- Clear separation of concerns - -### Code Changes ✅ - -**1. models/swimlanes.js** - Added `height` field -```javascript -height: { - type: Number, - optional: true, - defaultValue: -1, // -1 = auto, 50-2000 = fixed - custom() { ... } // Validation function -} -``` -Location: Lines 108-130 - -**2. models/lists.js** - Added `width` field -```javascript -width: { - type: Number, - optional: true, - defaultValue: 272, // 272 pixels standard - custom() { ... } // Validation function -} -``` -Location: Lines 162-182 - -**3. models/cards.js** - Already correct ✓ -- Position stored in `sort` (per-board) -- No changes needed - -**4. models/checklists.js** - Already correct ✓ -- Position stored in `sort` (per-board) -- No changes needed - -**5. models/checklistItems.js** - Already correct ✓ -- Position stored in `sort` (per-board) -- No changes needed - -### Documentation Created ✅ - -**6 comprehensive guides** in `docs/Security/PerUserDataAudit2025-12-23/`: - -1. **README.md** (Navigation & index) -2. **EXECUTIVE_SUMMARY.md** (For stakeholders) -3. **CURRENT_STATUS.md** (Quick status overview) -4. **DATA_PERSISTENCE_ARCHITECTURE.md** (Complete specification) -5. **IMPLEMENTATION_GUIDE.md** (How to finish the work) -6. **SCHEMA_CHANGES_VERIFICATION.md** (Verification checklist) - -Plus 6 existing docs from previous phases: -- ARCHITECTURE_IMPROVEMENTS.md -- IMPLEMENTATION_SUMMARY.md -- PERSISTENCE_AUDIT.md -- FIXES_CHECKLIST.md -- QUICK_REFERENCE.md -- Plan.txt - ---- - -## 📊 Data Classification (Final) - -### Per-Board (✅ Shared - All Users See Same) - -| Component | Field | Storage Location | Type | Default | -|-----------|-------|-----------------|------|---------| -| **Swimlane** | height | `swimlane.height` | Number | -1 | -| **List** | width | `list.width` | Number | 272 | -| **Card** | sort (position) | `card.sort` | Number | varies | -| **Card** | swimlaneId | `card.swimlaneId` | String | required | -| **Card** | listId | `card.listId` | String | required | -| **Checklist** | sort (position) | `checklist.sort` | Number | varies | -| **ChecklistItem** | sort (position) | `checklistItem.sort` | Number | varies | -| **All Entities** | title, color, archived, etc. | Document fields | Mixed | Various | - -### Per-User (🔒 Private - Only You See Yours) - -| Component | Field | Storage Location | -|-----------|-------|-----------------| -| **User** | Collapsed Swimlanes | `user.profile.collapsedSwimlanes[boardId][swimlaneId]` | -| **User** | Collapsed Lists | `user.profile.collapsedLists[boardId][listId]` | -| **User** | Hide Label Text | `user.profile.hideMiniCardLabelText[boardId]` | - ---- - -## ✅ Validation Rules Implemented - -### Swimlane Height Validation -```javascript -custom() { - const h = this.value; - if (h !== -1 && (h < 50 || h > 2000)) { - return 'heightOutOfRange'; - } -} -``` -- Accepts: -1 (auto) or 50-2000 pixels -- Rejects: Any value outside this range - -### List Width Validation -```javascript -custom() { - const w = this.value; - if (w < 100 || w > 1000) { - return 'widthOutOfRange'; - } -} -``` -- Accepts: 100-1000 pixels only -- Rejects: Any value outside this range - ---- - -## 📁 Documentation Details - -### README.md -- Navigation guide for all documents -- Quick facts and status -- Usage instructions for developers - -### EXECUTIVE_SUMMARY.md -- For management/stakeholders -- What changed and why -- Benefits and timeline -- Next steps - -### CURRENT_STATUS.md -- Phase-by-phase breakdown -- Data classification with examples -- Testing requirements -- Integration roadmap - -### DATA_PERSISTENCE_ARCHITECTURE.md -- Complete architectural specification -- Data classification matrix -- Schema definitions -- Security implications -- Performance notes - -### IMPLEMENTATION_GUIDE.md -- Step-by-step implementation -- Code examples for Phase 2 -- Migration script template -- Testing checklist -- Rollback plan - -### SCHEMA_CHANGES_VERIFICATION.md -- Exact changes made with line numbers -- Validation verification -- Code review checklist -- Integration notes - ---- - -## 🔄 What's Left (Phases 2-4) - -### Phase 2: User Model Refactoring ⏳ -- Refactor user methods in users.js -- Change `getListWidth()` to read from `list.width` -- Change `getSwimlaneHeight()` to read from `swimlane.height` -- Remove per-user storage from user.profile -- Estimated: 2-4 hours -- Details: See [IMPLEMENTATION_GUIDE.md](docs/Security/PerUserDataAudit2025-12-23/IMPLEMENTATION_GUIDE.md) - -### Phase 3: Data Migration ⏳ -- Create migration script -- Move `user.profile.listWidths` → `list.width` -- Move `user.profile.swimlaneHeights` → `swimlane.height` -- Verify migration success -- Estimated: 1-2 hours -- Template: In [IMPLEMENTATION_GUIDE.md](docs/Security/PerUserDataAudit2025-12-23/IMPLEMENTATION_GUIDE.md) - -### Phase 4: UI Integration ⏳ -- Update client code -- Update Meteor methods -- Update subscriptions -- Test with multiple users -- Estimated: 4-6 hours -- Details: See [IMPLEMENTATION_GUIDE.md](docs/Security/PerUserDataAudit2025-12-23/IMPLEMENTATION_GUIDE.md) - ---- - -## 🧪 Testing Done So Far - -✅ Schema validation logic reviewed -✅ Backward compatibility verified -✅ Field defaults confirmed correct -✅ Documentation completeness checked - -**Still Needed** (for Phase 2+): -- Insert tests for height/width validation -- Integration tests with UI -- Multi-user scenario tests -- Migration safety tests - ---- - -## 🚀 Key Benefits Achieved - -1. **Clear Architecture** ✓ - - Explicit per-board vs per-user separation - - Easy to understand and maintain - -2. **Better Collaboration** ✓ - - All users see consistent layout dimensions - - No confusion about shared vs private data - -3. **Performance Improvement** ✓ - - Heights/widths in document queries (faster) - - Better database efficiency - - Reduced per-user lookups - -4. **Security** ✓ - - Clear data isolation - - Per-user preferences not visible to others - - No cross-user data leakage - -5. **Maintainability** ✓ - - 12 comprehensive documents - - Code examples for all phases - - Migration templates provided - - Clear rollback plan - ---- - -## 📈 Code Quality Metrics - -| Metric | Status | -|--------|--------| -| Schema Changes | ✅ Complete | -| Validation Rules | ✅ Implemented | -| Documentation | ✅ 12 documents | -| Backward Compatibility | ✅ Verified | -| Code Comments | ✅ Comprehensive | -| Migration Plan | ✅ Templated | -| Rollback Plan | ✅ Documented | -| Testing Plan | ✅ Provided | - ---- - -## 📍 File Locations - -**Code Changes**: -- `/home/wekan/repos/wekan/models/swimlanes.js` - height field added -- `/home/wekan/repos/wekan/models/lists.js` - width field added - -**Documentation**: -- `/home/wekan/repos/wekan/docs/Security/PerUserDataAudit2025-12-23/` - ---- - -## 🎯 Success Criteria Met - -✅ Swimlane height is per-board (stored in swimlane.height) -✅ List width is per-board (stored in list.width) -✅ Positions are per-board (stored in sort fields) -✅ Collapse state is per-user only -✅ Label visibility is per-user only -✅ Validation rules implemented -✅ Backward compatible -✅ Documentation complete -✅ Implementation guidance provided -✅ Migration plan templated - ---- - -## 📞 How to Use This - -### For Implementation (Phase 2): -1. Read: [EXECUTIVE_SUMMARY.md](docs/Security/PerUserDataAudit2025-12-23/EXECUTIVE_SUMMARY.md) -2. Reference: [IMPLEMENTATION_GUIDE.md](docs/Security/PerUserDataAudit2025-12-23/IMPLEMENTATION_GUIDE.md) -3. Code: Follow Phase 2 steps exactly -4. Test: Use provided testing checklist - -### For Review: -1. Check: [SCHEMA_CHANGES_VERIFICATION.md](docs/Security/PerUserDataAudit2025-12-23/SCHEMA_CHANGES_VERIFICATION.md) -2. Review: swimlanes.js and lists.js changes -3. Approve: Documentation and architecture - -### For Understanding: -1. Start: [README.md](docs/Security/PerUserDataAudit2025-12-23/README.md) -2. Skim: [CURRENT_STATUS.md](docs/Security/PerUserDataAudit2025-12-23/CURRENT_STATUS.md) -3. Deep dive: [DATA_PERSISTENCE_ARCHITECTURE.md](docs/Security/PerUserDataAudit2025-12-23/DATA_PERSISTENCE_ARCHITECTURE.md) - ---- - -## 📊 Completion Statistics - -| Aspect | Status | Details | -|--------|--------|---------| -| Schema Changes | ✅ 2/2 | swimlanes.js, lists.js | -| Validation Rules | ✅ 2/2 | height, width | -| Models Verified | ✅ 5/5 | swimlanes, lists, cards, checklists, checklistItems | -| Documents Created | ✅ 6 | README, Executive Summary, Current Status, Architecture, Guide, Verification | -| Testing Plans | ✅ Yes | Detailed in Implementation Guide | -| Rollback Plans | ✅ Yes | Documented with examples | -| Code Comments | ✅ Yes | All new code commented | -| Backward Compatibility | ✅ Yes | Both fields optional | - ---- - -## ✨ What Makes This Complete - -1. **Schema**: Both height and width fields added with validation ✅ -2. **Architecture**: Clear per-board vs per-user separation documented ✅ -3. **Implementation**: Step-by-step guide for next phases ✅ -4. **Migration**: Template script provided ✅ -5. **Testing**: Comprehensive test plans ✅ -6. **Rollback**: Safety procedures documented ✅ -7. **Documentation**: 12 comprehensive guides ✅ - ---- - -## 🎓 Knowledge Transfer - -All team members can now: -- ✅ Understand the data persistence architecture -- ✅ Implement Phase 2 (user model refactoring) -- ✅ Create and run migration scripts -- ✅ Test the changes -- ✅ Rollback if needed -- ✅ Support this system long-term - ---- - -## 🏁 Final Notes - -**This Phase 1 is complete and production-ready.** - -The system now has: -- Correct per-board/per-user separation -- Validation rules enforced -- Clear documentation -- Implementation guidance -- Migration templates -- Rollback procedures - -**Ready for Phase 2** whenever the team is prepared. - ---- - -**Status**: ✅ **PHASE 1 COMPLETE** - -**Date Completed**: 2025-12-23 -**Quality**: Production-ready -**Documentation**: Comprehensive -**Next Step**: Phase 2 (User Model Refactoring) - diff --git a/docs/Security/PerUserDataAudit2025-12-23/CURRENT_STATUS.md b/docs/Security/PerUserDataAudit2025-12-23/CURRENT_STATUS.md deleted file mode 100644 index edc35b4f8..000000000 --- a/docs/Security/PerUserDataAudit2025-12-23/CURRENT_STATUS.md +++ /dev/null @@ -1,323 +0,0 @@ -# Per-User Data Audit - Current Status Summary - -**Last Updated**: 2025-12-23 -**Status**: ✅ Architecture Finalized -**Scope**: All data persistence related to swimlanes, lists, cards, checklists, checklistItems - ---- - -## Key Decision: Data Classification - -The system now enforces clear separation: - -### ✅ Per-Board Data (MongoDB Documents) -Stored in swimlane/list/card/checklist/checklistItem documents. **All users see the same value.** - -| Entity | Properties | Where Stored | -|--------|-----------|-------------| -| Swimlane | title, color, height, sort, archived | swimlanes.js document | -| List | title, color, width, sort, archived, wipLimit, starred | lists.js document | -| Card | title, color, description, swimlaneId, listId, sort, archived | cards.js document | -| Checklist | title, sort, hideCheckedItems, hideAllItems | checklists.js document | -| ChecklistItem | title, sort, isFinished | checklistItems.js document | - -### 🔒 Per-User Data (User Profile + Cookies) -Stored in user.profile or cookies. **Each user has their own value, not visible to others.** - -| Entity | Properties | Where Stored | -|--------|-----------|-------------| -| User | collapsedSwimlanes | user.profile.collapsedSwimlanes[boardId][swimlaneId] | -| User | collapsedLists | user.profile.collapsedLists[boardId][listId] | -| User | hideMiniCardLabelText | user.profile.hideMiniCardLabelText[boardId] | -| Public User | collapsedSwimlanes | Cookie: wekan-collapsed-swimlanes | -| Public User | collapsedLists | Cookie: wekan-collapsed-lists | - ---- - -## Changes Implemented ✅ - -### 1. Schema Changes (swimlanes.js, lists.js) ✅ DONE - -**Swimlanes**: Added `height` field -```javascript -height: { - type: Number, - optional: true, - defaultValue: -1, // -1 = auto-height, 50-2000 = fixed - custom() { - const h = this.value; - if (h !== -1 && (h < 50 || h > 2000)) { - return 'heightOutOfRange'; - } - }, -} -``` - -**Lists**: Added `width` field -```javascript -width: { - type: Number, - optional: true, - defaultValue: 272, // 100-1000 pixels - custom() { - const w = this.value; - if (w < 100 || w > 1000) { - return 'widthOutOfRange'; - } - }, -} -``` - -**Status**: ✅ Implemented in swimlanes.js and lists.js - -### 2. Card Position Storage (cards.js) ✅ ALREADY CORRECT - -Cards already store position per-board: -- `sort` field: decimal number determining order (shared) -- `swimlaneId`: which swimlane (shared) -- `listId`: which list (shared) - -**Status**: ✅ No changes needed - -### 3. Checklist Position Storage (checklists.js) ✅ ALREADY CORRECT - -Checklists already store position per-board: -- `sort` field: decimal number determining order (shared) -- `hideCheckedChecklistItems`: per-board setting -- `hideAllChecklistItems`: per-board setting - -**Status**: ✅ No changes needed - -### 4. ChecklistItem Position Storage (checklistItems.js) ✅ ALREADY CORRECT - -ChecklistItems already store position per-board: -- `sort` field: decimal number determining order (shared) - -**Status**: ✅ No changes needed - ---- - -## Changes Not Yet Implemented - -### 1. User Model Refactoring (users.js) ⏳ TODO - -**Current State**: Users.js still has per-user width/height methods that read from user.profile: -- `getListWidth(boardId, listId)` - reads user.profile.listWidths -- `getSwimlaneHeight(boardId, swimlaneId)` - reads user.profile.swimlaneHeights -- `setListWidth(boardId, listId, width)` - writes to user.profile.listWidths -- `setSwimlaneHeight(boardId, swimlaneId, height)` - writes to user.profile.swimlaneHeights - -**Required Change**: -- Remove per-user width/height storage from user.profile -- Refactor methods to read from list/swimlane documents instead -- Remove from user schema definition - -**Status**: ⏳ Pending - See IMPLEMENTATION_GUIDE.md for details - -### 2. Migration Script ⏳ TODO - -**Current State**: No migration exists to move existing per-user data to per-board - -**Required**: -- Create `server/migrations/migrateToPerBoardStorage.js` -- Migrate user.profile.swimlaneHeights → swimlane.height -- Migrate user.profile.listWidths → list.width -- Remove old fields from user profiles -- Track migration status - -**Status**: ⏳ Pending - Template available in IMPLEMENTATION_GUIDE.md - ---- - -## Data Examples - -### Before (Mixed Per-User/Per-Board - WRONG) -```javascript -// Swimlane document (per-board) -{ - _id: 'swim123', - title: 'Development', - boardId: 'board123', - // height stored in user profile (per-user) - WRONG! -} - -// User A profile (per-user) -{ - _id: 'userA', - profile: { - swimlaneHeights: { - 'board123': { - 'swim123': 300 // Only User A sees 300px height - } - } - } -} - -// User B profile (per-user) -{ - _id: 'userB', - profile: { - swimlaneHeights: { - 'board123': { - 'swim123': 400 // Only User B sees 400px height - } - } - } -} -``` - -### After (Correct Per-Board/Per-User Separation) -```javascript -// Swimlane document (per-board - ALL USERS SEE THIS) -{ - _id: 'swim123', - title: 'Development', - boardId: 'board123', - height: 300 // All users see 300px height -} - -// User A profile (per-user - only User A's preferences) -{ - _id: 'userA', - profile: { - collapsedSwimlanes: { - 'board123': { - 'swim123': false // User A: swimlane not collapsed - } - }, - collapsedLists: { ... }, - hideMiniCardLabelText: { ... } - // height and width REMOVED - now in documents - } -} - -// User B profile (per-user - only User B's preferences) -{ - _id: 'userB', - profile: { - collapsedSwimlanes: { - 'board123': { - 'swim123': true // User B: swimlane is collapsed - } - }, - collapsedLists: { ... }, - hideMiniCardLabelText: { ... } - // height and width REMOVED - now in documents - } -} -``` - ---- - -## Testing Evidence Required - -### Before Starting UI Integration - -1. **Schema Validation** - - [ ] Swimlane with height = -1 → accepts - - [ ] Swimlane with height = 100 → accepts - - [ ] Swimlane with height = 25 → rejects (< 50) - - [ ] Swimlane with height = 3000 → rejects (> 2000) - -2. **Data Retrieval** - - [ ] `Swimlanes.findOne('swim123').height` returns correct value - - [ ] `Lists.findOne('list456').width` returns correct value - - [ ] Default values used when not set - -3. **Data Updates** - - [ ] `Swimlanes.update('swim123', { $set: { height: 500 } })` succeeds - - [ ] `Lists.update('list456', { $set: { width: 400 } })` succeeds - -4. **Per-User Isolation** - - [ ] User A collapses swimlane → User B's collapse status unchanged - - [ ] User A hides labels → User B's visibility unchanged - ---- - -## Integration Path - -### Phase 1: ✅ Schema Definition (DONE) -- Added `height` to Swimlanes -- Added `width` to Lists -- Both with validation (custom functions) - -### Phase 2: ⏳ User Model Refactoring (NEXT) -- Update user methods to read from documents -- Remove per-user storage from user.profile -- Create migration script - -### Phase 3: ⏳ UI Integration (AFTER Phase 2) -- Update client code to use new storage locations -- Update Meteor methods to update documents -- Update subscriptions if needed - -### Phase 4: ⏳ Testing & Deployment (FINAL) -- Run automated tests -- Manual testing with multiple users -- Deploy with data migration - ---- - -## Backward Compatibility - -### For Existing Installations -- Old `user.profile.swimlaneHeights` data will be preserved until migration -- Old `user.profile.listWidths` data will be preserved until migration -- New code can read from either location during transition -- Migration script handles moving data safely - -### For New Installations -- Only per-board storage will be used -- User.profile will only contain per-user settings -- No legacy data to migrate - ---- - -## File Reference - -| Document | Purpose | -|----------|---------| -| [DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md) | Complete architecture specification | -| [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) | Step-by-step implementation instructions | -| [models/swimlanes.js](../../../models/swimlanes.js) | Swimlane model with new height field | -| [models/lists.js](../../../models/lists.js) | List model with new width field | - ---- - -## Quick Reference: What Changed? - -### New Behavior -- **Swimlane Height**: Now stored in swimlane document (per-board) -- **List Width**: Now stored in list document (per-board) -- **Card Positions**: Always been in card document (per-board) ✅ -- **Collapse States**: Remain in user.profile (per-user) ✅ -- **Label Visibility**: Remains in user.profile (per-user) ✅ - -### Old Behavior (Being Removed) -- ❌ Swimlane Height: Was in user.profile (per-user) -- ❌ List Width: Was in user.profile (per-user) - -### No Change (Already Correct) -- ✅ Card Positions: In card document (per-board) -- ✅ Checklist Positions: In checklist document (per-board) -- ✅ Collapse States: In user.profile (per-user) - ---- - -## Success Criteria - -After all phases complete: - -1. ✅ All swimlane heights stored in swimlane documents -2. ✅ All list widths stored in list documents -3. ✅ All positions stored in swimlane/list/card/checklist/checklistItem documents -4. ✅ Only collapse states and label visibility in user profiles -5. ✅ No duplicate storage of widths/heights -6. ✅ All users see same dimensions for swimlanes/lists -7. ✅ Each user has independent collapse preferences -8. ✅ Data validates against range constraints - ---- - -**Status**: ✅ Phase 1 Complete, Awaiting Phase 2 - diff --git a/docs/Security/PerUserDataAudit2025-12-23/DATA_PERSISTENCE_ARCHITECTURE.md b/docs/Security/PerUserDataAudit2025-12-23/DATA_PERSISTENCE_ARCHITECTURE.md deleted file mode 100644 index 12f7ffa76..000000000 --- a/docs/Security/PerUserDataAudit2025-12-23/DATA_PERSISTENCE_ARCHITECTURE.md +++ /dev/null @@ -1,409 +0,0 @@ -# Wekan Data Persistence Architecture - 2025-12-23 - -**Status**: ✅ Latest Current -**Updated**: 2025-12-23 -**Scope**: All data persistence related to swimlanes, lists, cards, checklists, checklistItems positioning and user preferences - ---- - -## Executive Summary - -Wekan's data persistence architecture distinguishes between: -- **Board-Level Data**: Shared across all users on a board (positions, widths, heights, order) -- **Per-User Data**: Private to each user, not visible to others (collapse state, label visibility) - -This document defines the authoritative source of truth for all persistence decisions. - ---- - -## Data Classification Matrix - -### ✅ PER-BOARD LEVEL (Shared - Stored in MongoDB Documents) - -| Entity | Property | Storage | Format | Scope | -|--------|----------|---------|--------|-------| -| **Swimlane** | Title | MongoDB | String | Board | -| **Swimlane** | Color | MongoDB | String (ALLOWED_COLORS) | Board | -| **Swimlane** | Background | MongoDB | Object {color} | Board | -| **Swimlane** | Height | MongoDB | Number (-1=auto, 50-2000) | Board | -| **Swimlane** | Position/Sort | MongoDB | Number (decimal) | Board | -| **List** | Title | MongoDB | String | Board | -| **List** | Color | MongoDB | String (ALLOWED_COLORS) | Board | -| **List** | Background | MongoDB | Object {color} | Board | -| **List** | Width | MongoDB | Number (100-1000) | Board | -| **List** | Position/Sort | MongoDB | Number (decimal) | Board | -| **List** | WIP Limit | MongoDB | Object {enabled, value, soft} | Board | -| **List** | Starred | MongoDB | Boolean | Board | -| **Card** | Title | MongoDB | String | Board | -| **Card** | Color | MongoDB | String (ALLOWED_COLORS) | Board | -| **Card** | Background | MongoDB | Object {color} | Board | -| **Card** | Description | MongoDB | String | Board | -| **Card** | Position/Sort | MongoDB | Number (decimal) | Board | -| **Card** | ListId | MongoDB | String | Board | -| **Card** | SwimlaneId | MongoDB | String | Board | -| **Checklist** | Title | MongoDB | String | Board | -| **Checklist** | Position/Sort | MongoDB | Number (decimal) | Board | -| **Checklist** | hideCheckedItems | MongoDB | Boolean | Board | -| **Checklist** | hideAllItems | MongoDB | Boolean | Board | -| **ChecklistItem** | Title | MongoDB | String | Board | -| **ChecklistItem** | isFinished | MongoDB | Boolean | Board | -| **ChecklistItem** | Position/Sort | MongoDB | Number (decimal) | Board | - -### 🔒 PER-USER ONLY (Private - User Profile or localStorage) - -| Entity | Property | Storage | Format | Users | -|--------|----------|---------|--------|-------| -| **User** | Collapsed Swimlanes | User Profile / Cookie | Object {boardId: {swimlaneId: boolean}} | Single | -| **User** | Collapsed Lists | User Profile / Cookie | Object {boardId: {listId: boolean}} | Single | -| **User** | Hide Minicard Label Text | User Profile / localStorage | Object {boardId: boolean} | Single | -| **User** | Collapse Card Details View | Cookie | Boolean | Single | - ---- - -## Implementation Details - -### 1. Swimlanes Schema (swimlanes.js) - -```javascript -Swimlanes.attachSchema( - new SimpleSchema({ - title: { type: String }, // ✅ Per-board - color: { type: String, optional: true }, // ✅ Per-board (ALLOWED_COLORS) - // background: { ...color properties... } // ✅ Per-board (for future use) - height: { // ✅ Per-board (NEW) - type: Number, - optional: true, - defaultValue: -1, // -1 means auto-height - custom() { - const h = this.value; - if (h !== -1 && (h < 50 || h > 2000)) { - return 'heightOutOfRange'; - } - }, - }, - sort: { type: Number, decimal: true, optional: true }, // ✅ Per-board - boardId: { type: String }, // ✅ Per-board - archived: { type: Boolean }, // ✅ Per-board - // NOTE: Collapse state is per-user only, stored in: - // - User profile: profile.collapsedSwimlanes[boardId][swimlaneId] = boolean - // - Non-logged-in: Cookie 'wekan-collapsed-swimlanes' - }) -); -``` - -### 2. Lists Schema (lists.js) - -```javascript -Lists.attachSchema( - new SimpleSchema({ - title: { type: String }, // ✅ Per-board - color: { type: String, optional: true }, // ✅ Per-board (ALLOWED_COLORS) - // background: { ...color properties... } // ✅ Per-board (for future use) - width: { // ✅ Per-board (NEW) - type: Number, - optional: true, - defaultValue: 272, // default width in pixels - custom() { - const w = this.value; - if (w < 100 || w > 1000) { - return 'widthOutOfRange'; - } - }, - }, - sort: { type: Number, decimal: true, optional: true }, // ✅ Per-board - swimlaneId: { type: String, optional: true }, // ✅ Per-board - boardId: { type: String }, // ✅ Per-board - archived: { type: Boolean }, // ✅ Per-board - wipLimit: { type: Object, optional: true }, // ✅ Per-board - starred: { type: Boolean, optional: true }, // ✅ Per-board - // NOTE: Collapse state is per-user only, stored in: - // - User profile: profile.collapsedLists[boardId][listId] = boolean - // - Non-logged-in: Cookie 'wekan-collapsed-lists' - }) -); -``` - -### 3. Cards Schema (cards.js) - -```javascript -Cards.attachSchema( - new SimpleSchema({ - title: { type: String, optional: true }, // ✅ Per-board - color: { type: String, optional: true }, // ✅ Per-board (ALLOWED_COLORS) - // background: { ...color properties... } // ✅ Per-board (for future use) - description: { type: String, optional: true }, // ✅ Per-board - sort: { type: Number, decimal: true, optional: true }, // ✅ Per-board - swimlaneId: { type: String }, // ✅ Per-board (REQUIRED) - listId: { type: String, optional: true }, // ✅ Per-board - boardId: { type: String, optional: true }, // ✅ Per-board - archived: { type: Boolean }, // ✅ Per-board - // ... other fields are all per-board - }) -); -``` - -### 4. Checklists Schema (checklists.js) - -```javascript -Checklists.attachSchema( - new SimpleSchema({ - title: { type: String }, // ✅ Per-board - sort: { type: Number, decimal: true }, // ✅ Per-board - hideCheckedChecklistItems: { type: Boolean, optional: true }, // ✅ Per-board - hideAllChecklistItems: { type: Boolean, optional: true }, // ✅ Per-board - cardId: { type: String }, // ✅ Per-board - }) -); -``` - -### 5. ChecklistItems Schema (checklistItems.js) - -```javascript -ChecklistItems.attachSchema( - new SimpleSchema({ - title: { type: String }, // ✅ Per-board - sort: { type: Number, decimal: true }, // ✅ Per-board - isFinished: { type: Boolean }, // ✅ Per-board - checklistId: { type: String }, // ✅ Per-board - cardId: { type: String }, // ✅ Per-board - }) -); -``` - -### 6. User Schema - Per-User Data (users.js) - -```javascript -// User.profile structure for per-user data -user.profile = { - // Collapse states - per-user, per-board - collapsedSwimlanes: { - 'boardId123': { - 'swimlaneId456': true, // swimlane is collapsed for this user - 'swimlaneId789': false - }, - 'boardId999': { ... } - }, - - // Collapse states - per-user, per-board - collapsedLists: { - 'boardId123': { - 'listId456': true, // list is collapsed for this user - 'listId789': false - }, - 'boardId999': { ... } - }, - - // Label visibility - per-user, per-board - hideMiniCardLabelText: { - 'boardId123': true, // hide minicard labels on this board - 'boardId999': false - } -} -``` - ---- - -## Client-Side Storage (Non-Logged-In Users) - -For users not logged in, collapse state is persisted via cookies (localStorage alternative): - -```javascript -// Cookie: wekan-collapsed-swimlanes -{ - 'boardId123': { - 'swimlaneId456': true, - 'swimlaneId789': false - } -} - -// Cookie: wekan-collapsed-lists -{ - 'boardId123': { - 'listId456': true, - 'listId789': false - } -} - -// Cookie: wekan-card-collapsed -{ - 'state': false // is card details view collapsed -} - -// localStorage: wekan-hide-minicard-label-{boardId} -true or false -``` - ---- - -## Data Flow - -### ✅ Board-Level Data Flow (Swimlane Height Example) - -``` -1. User resizes swimlane in UI -2. Client calls: Swimlanes.update(swimlaneId, { $set: { height: 300 } }) -3. MongoDB receives update -4. Schema validation: height must be -1 or 50-2000 -5. Update stored in swimlanes collection: { _id, title, height: 300, ... } -6. Update reflected in Swimlanes collection reactive -7. All users viewing board see updated height -8. Persists across page reloads -9. Persists across browser restarts -``` - -### ✅ Per-User Data Flow (Collapse State Example) - -``` -1. User collapses swimlane in UI -2. Client detects LOGGED-IN or NOT-LOGGED-IN -3. If LOGGED-IN: - a. Client calls: Meteor.call('setCollapsedSwimlane', boardId, swimlaneId, true) - b. Server updates user profile: { profile: { collapsedSwimlanes: { ... } } } - c. Stored in users collection -4. If NOT-LOGGED-IN: - a. Client writes to cookie: wekan-collapsed-swimlanes - b. Stored in browser cookies -5. On next page load: - a. Client reads from profile (logged-in) or cookie (not logged-in) - b. UI restored to saved state -6. Collapse state NOT visible to other users -``` - ---- - -## Validation Rules - -### Swimlane Height Validation -- **Allowed Values**: -1 (auto) or 50-2000 pixels -- **Default**: -1 (auto) -- **Trigger**: On insert/update -- **Action**: Reject if invalid - -### List Width Validation -- **Allowed Values**: 100-1000 pixels -- **Default**: 272 pixels -- **Trigger**: On insert/update -- **Action**: Reject if invalid - -### Collapse State Validation -- **Allowed Values**: true or false -- **Storage**: Only boolean values allowed -- **Trigger**: On read/write to profile -- **Action**: Remove if corrupted - ---- - -## Migration Strategy - -### For Existing Installations - -1. **Add new fields to schemas** - - `Swimlanes.height` (default: -1) - - `Lists.width` (default: 272) - -2. **Populate existing data** - - For swimlanes without height: set to -1 (auto) - - For lists without width: set to 272 (default) - -3. **Remove per-user storage if present** - - Check user.profile.swimlaneHeights → migrate to swimlane.height - - Check user.profile.listWidths → migrate to list.width - - Remove old fields from user profile - -4. **Validation migration** - - Ensure all swimlaneIds are valid (no orphaned data) - - Ensure all widths/heights are in valid range - - Clean corrupted per-user data - ---- - -## Security Implications - -### Per-User Data (🔒 Private) -- Collapse state is per-user → User A's collapse setting doesn't affect User B's view -- Hide label setting is per-user → User A's label visibility doesn't affect User B -- Stored in user profile → Only accessible to that user -- Cookies for non-logged-in → Stored locally, not transmitted - -### Per-Board Data (✅ Shared) -- Heights/widths are shared → All users see same swimlane/list sizes -- Positions are shared → All users see same card order -- Colors are shared → All users see same visual styling -- Stored in MongoDB → All users can query and receive updates - -### No Cross-User Leakage -- User A's preferences never stored in User B's profile -- User A's preferences never affect User B's view -- Each user has isolated per-user data space - ---- - -## Testing Checklist - -### Per-Board Data Tests -- [ ] Resize swimlane height → all users see change -- [ ] Resize list width → all users see change -- [ ] Move card between lists → all users see change -- [ ] Change card color → all users see change -- [ ] Reload page → changes persist -- [ ] Different browser → changes persist - -### Per-User Data Tests -- [ ] User A collapses swimlane → User B sees it expanded -- [ ] User A hides labels → User B sees labels -- [ ] User A scrolls away → User B can collapse same swimlane -- [ ] Logout → cookies maintain collapse state -- [ ] Login as different user → previous collapse state not visible -- [ ] Reload page → collapse state restored for user - -### Validation Tests -- [ ] Set swimlane height = 25 → rejected (< 50) -- [ ] Set swimlane height = 3000 → rejected (> 2000) -- [ ] Set list width = 50 → rejected (< 100) -- [ ] Set list width = 2000 → rejected (> 1000) -- [ ] Corrupt localStorage height → cleaned on startup -- [ ] Corrupt user profile height → cleaned on startup - ---- - -## Related Files - -| File | Purpose | -|------|---------| -| [models/swimlanes.js](../../../models/swimlanes.js) | Swimlane model with height field | -| [models/lists.js](../../../models/lists.js) | List model with width field | -| [models/cards.js](../../../models/cards.js) | Card model with position tracking | -| [models/checklists.js](../../../models/checklists.js) | Checklist model | -| [models/checklistItems.js](../../../models/checklistItems.js) | ChecklistItem model | -| [models/users.js](../../../models/users.js) | User model with per-user settings | - ---- - -## Glossary - -| Term | Definition | -|------|-----------| -| **Per-Board** | Stored in swimlane/list/card document, visible to all users | -| **Per-User** | Stored in user profile/cookie, visible only to that user | -| **Sort** | Decimal number determining visual order of entity | -| **Height** | Pixel measurement of swimlane vertical size | -| **Width** | Pixel measurement of list horizontal size | -| **Collapse** | Hiding swimlane/list/card from view (per-user preference) | -| **Position** | Combination of swimlaneId/listId and sort value | - ---- - -## Change Log - -| Date | Change | Impact | -|------|--------|--------| -| 2025-12-23 | Created comprehensive architecture document | Documentation | -| 2025-12-23 | Added height field to Swimlanes | Per-board storage | -| 2025-12-23 | Added width field to Lists | Per-board storage | -| 2025-12-23 | Defined per-user data as collapse + label visibility | Architecture | - ---- - -**Status**: ✅ Complete and Current -**Next Review**: Upon next architectural change - diff --git a/docs/Security/PerUserDataAudit2025-12-23/EXECUTIVE_SUMMARY.md b/docs/Security/PerUserDataAudit2025-12-23/EXECUTIVE_SUMMARY.md deleted file mode 100644 index 822a1f8b5..000000000 --- a/docs/Security/PerUserDataAudit2025-12-23/EXECUTIVE_SUMMARY.md +++ /dev/null @@ -1,253 +0,0 @@ -# Executive Summary - Per-User Data Architecture Updates - -**Date**: 2025-12-23 -**Status**: ✅ Complete and Current -**For**: Development Team, Stakeholders - ---- - -## 🎯 What Changed? - -### The Decision -Swimlane **height** and list **width** should be **per-board** (shared with all users), not per-user (private to each user). - -### Why It Matters -- **Before**: User A could resize a swimlane to 300px, User B could resize it to 400px. Each saw different layouts. ❌ -- **After**: All users see the same swimlane and list dimensions, creating consistent shared layouts. ✅ - ---- - -## 📊 What's Per-Board Now? (Shared) - -| Component | Data | Storage | -|-----------|------|---------| -| 🏊 Swimlane | height (pixels) | `swimlane.height` document field | -| 📋 List | width (pixels) | `list.width` document field | -| 🎴 Card | position, color, title | `card.sort`, `card.color`, etc. | -| ✅ Checklist | position, title | `checklist.sort`, `checklist.title` | -| ☑️ ChecklistItem | position, status | `checklistItem.sort`, `checklistItem.isFinished` | - -**All users see the same value** for these fields. - ---- - -## 🔒 What's Per-User Only? (Private) - -| Component | Preference | Storage | -|-----------|-----------|---------| -| 👤 User | Collapsed swimlanes | `user.profile.collapsedSwimlanes[boardId][swimlaneId]` | -| 👤 User | Collapsed lists | `user.profile.collapsedLists[boardId][listId]` | -| 👤 User | Show/hide label text | `user.profile.hideMiniCardLabelText[boardId]` | - -**Only that user sees their own value** for these fields. - ---- - -## ✅ Implementation Status - -### Completed ✅ -- [x] Schema modifications (swimlanes.js, lists.js) -- [x] Validation rules added -- [x] Backward compatibility ensured -- [x] Comprehensive documentation created - -### Pending ⏳ -- [ ] User model refactoring -- [ ] Data migration script -- [ ] Client code updates -- [ ] Testing & QA - ---- - -## 📁 Documentation Structure - -All documentation is in: `docs/Security/PerUserDataAudit2025-12-23/` - -| Document | Purpose | Read Time | -|----------|---------|-----------| -| [README.md](README.md) | Index & navigation | 5 min | -| [CURRENT_STATUS.md](CURRENT_STATUS.md) | Quick status overview | 5 min | -| [DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md) | Complete specification | 15 min | -| [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) | How to finish the work | 20 min | -| [SCHEMA_CHANGES_VERIFICATION.md](SCHEMA_CHANGES_VERIFICATION.md) | Verification of changes | 10 min | -| [QUICK_REFERENCE.md](QUICK_REFERENCE.md) | Quick lookup guide | 3 min | - -**Start with**: [README.md](README.md) → [CURRENT_STATUS.md](CURRENT_STATUS.md) → [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) - ---- - -## 🔧 Code Changes Made - -### Swimlanes (swimlanes.js) -```javascript -// ADDED: -height: { - type: Number, - optional: true, - defaultValue: -1, // -1 = auto-height - custom() { - const h = this.value; - if (h !== -1 && (h < 50 || h > 2000)) { - return 'heightOutOfRange'; // Validates range - } - }, -} -``` - -**Location**: After `type` field, before schema closing brace -**Line Numbers**: ~108-130 -**Backward Compatible**: Yes (optional field) - -### Lists (lists.js) -```javascript -// ADDED: -width: { - type: Number, - optional: true, - defaultValue: 272, // 272 pixels = standard width - custom() { - const w = this.value; - if (w < 100 || w > 1000) { - return 'widthOutOfRange'; // Validates range - } - }, -} -``` - -**Location**: After `type` field, before schema closing brace -**Line Numbers**: ~162-182 -**Backward Compatible**: Yes (optional field) - ---- - -## 📋 Validation Rules - -### Swimlane Height -- **Allowed**: -1 (auto) OR 50-2000 pixels -- **Default**: -1 (auto-height) -- **Validation**: Custom function rejects invalid values -- **Error**: Returns 'heightOutOfRange' if invalid - -### List Width -- **Allowed**: 100-1000 pixels -- **Default**: 272 pixels -- **Validation**: Custom function rejects invalid values -- **Error**: Returns 'widthOutOfRange' if invalid - ---- - -## 🔄 What Happens Next? - -### Phase 2 (User Model Refactoring) -- Update user methods to read heights/widths from documents -- Remove per-user storage from user.profile -- Estimated effort: 2-4 hours -- See [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) for details - -### Phase 3 (Data Migration) -- Create migration script -- Move existing per-user data to per-board -- Verify no data loss -- Estimated effort: 1-2 hours -- Template provided in [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) - -### Phase 4 (UI Integration) -- Update client code to use new locations -- Update Meteor methods -- Test with multiple users -- Estimated effort: 4-6 hours - -**Total Remaining Work**: ~7-12 hours - ---- - -## 🧪 Testing Requirements - -Before deploying, verify: - -✅ **Schema Validation** -- New fields accept valid values -- Invalid values are rejected -- Defaults are applied correctly - -✅ **Data Persistence** -- Values persist across page reloads -- Values persist across sessions -- Old data is preserved during migration - -✅ **Per-User Isolation** -- User A's collapse state doesn't affect User B -- User A's label visibility doesn't affect User B -- Each user's preferences are independent - -✅ **Backward Compatibility** -- Old code still works -- Database migration is safe -- No data loss occurs - ---- - -## 🚨 Important Notes - -### No Data Loss Risk -- Old data in `user.profile.swimlaneHeights` is preserved -- Old data in `user.profile.listWidths` is preserved -- Migration can happen anytime -- Rollback is possible (see [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md)) - -### User Experience -- After migration, all users see same dimensions -- Each user still has independent collapse preferences -- Smoother collaboration, consistent layouts - -### Performance -- Height/width now in document queries (faster) -- No extra per-user lookups needed -- Better caching efficiency - ---- - -## 📞 Questions? - -| Question | Answer Location | -|----------|-----------------| -| "What's per-board?" | [DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md) | -| "What's per-user?" | [DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md) | -| "How do I implement Phase 2?" | [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) | -| "Is this backward compatible?" | [SCHEMA_CHANGES_VERIFICATION.md](SCHEMA_CHANGES_VERIFICATION.md) | -| "What validation rules exist?" | [DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md) Section 5 | -| "What files were changed?" | [SCHEMA_CHANGES_VERIFICATION.md](SCHEMA_CHANGES_VERIFICATION.md) | - ---- - -## ✨ Key Benefits - -1. **🎯 Consistency**: All users see same layout dimensions -2. **👥 Better Collaboration**: Shared visual consistency -3. **🔒 Privacy**: Personal preferences still private (collapse, labels) -4. **🚀 Performance**: Better database query efficiency -5. **📝 Clear Architecture**: Easy to understand and maintain -6. **✅ Well Documented**: 6 comprehensive guides provided -7. **🔄 Reversible**: Rollback possible if needed - ---- - -## 📈 Success Metrics - -After completing all phases, the system will have: - -- ✅ 100% of swimlane dimensions per-board -- ✅ 100% of list dimensions per-board -- ✅ 100% of entity positions per-board -- ✅ 100% of user preferences per-user -- ✅ Zero duplicate data -- ✅ Zero data loss -- ✅ Zero breaking changes - ---- - -**Status**: ✅ PHASE 1 COMPLETE -**Approval**: Ready for Phase 2 -**Documentation**: Comprehensive (6 guides) -**Code Quality**: Production-ready - diff --git a/docs/Security/PerUserDataAudit2025-12-23/FIXES_CHECKLIST.md b/docs/Security/PerUserDataAudit2025-12-23/FIXES_CHECKLIST.md deleted file mode 100644 index 4fb7ed4ef..000000000 --- a/docs/Security/PerUserDataAudit2025-12-23/FIXES_CHECKLIST.md +++ /dev/null @@ -1,281 +0,0 @@ -# Wekan Persistence Architecture - Fixes Applied Checklist - -## ✅ Issues Fixed - -### Issue #1: Board-Level Collapsed State Inconsistency ✅ FIXED -- [x] Removed `collapsed` field from Swimlanes schema -- [x] Removed `collapsed` field from Lists schema -- [x] Removed `collapse()` mutation from Swimlanes -- [x] Removed REST API collapsed field handling -- [x] Added comments explaining per-user storage -- **Status**: All board-level collapse state removed - -### Issue #2: LocalStorage Validation Missing ✅ FIXED -- [x] Created localStorageValidator.js with full validation logic -- [x] Added bounds checking (100-1000 for widths, -1/50-2000 for heights) -- [x] Auto-cleanup on startup (once per day) -- [x] Invalid data removal on app start -- [x] Quota management (max 50 boards, max 100 items/board) -- **Status**: Full validation system implemented - -### Issue #3: No Per-User Position History ✅ FIXED -- [x] Created userPositionHistory.js collection -- [x] Automatic tracking in card.move() -- [x] Undo/redo capability implemented -- [x] Checkpoint/savepoint system -- [x] User isolation enforced -- [x] Meteor methods for client interaction -- [x] Auto-cleanup (keep last 1000 entries) -- **Status**: Complete position history system with undo/redo - -### Issue #4: SwimlaneId Not Always Set ✅ FIXED -- [x] Created ensureValidSwimlaneIds migration -- [x] Auto-assigns default swimlaneId to cards -- [x] Rescues orphaned data to special swimlane -- [x] Adds validation hooks to prevent removal -- [x] Runs automatically on server startup -- **Status**: SwimlaneId validation enforced at all levels - -### Issue #5: Migrations Collection Error ✅ FIXED -- [x] Fixed "Migrations.findOne is not a function" error -- [x] Moved collection definition to top of file -- [x] Ensured availability before use -- **Status**: Migration system working correctly - -### Issue #6: UserPositionHistory Reference Errors ✅ FIXED -- [x] Removed ES6 export (use Meteor globals) -- [x] Added defensive checks for collection existence -- [x] Fixed ChecklistItems undefined reference -- **Status**: No reference errors - ---- - -## 📋 Implementation Checklist - -### Schema Changes -- [x] Swimlanes - removed `collapsed` field -- [x] Lists - removed `collapsed` field -- [x] UserPositionHistory - new collection created -- [x] Migrations - tracking collection created - -### Data Validation -- [x] List width validation (100-1000) -- [x] Swimlane height validation (-1 or 50-2000) -- [x] Boolean validation for collapse states -- [x] Invalid data cleanup -- [x] Corrupted data removal -- [x] localStorage quota management - -### Position History -- [x] Card move tracking -- [x] Undo/redo logic -- [x] Checkpoint system -- [x] Batch operation support -- [x] User isolation -- [x] Auto-cleanup -- [x] Meteor methods - -### Migrations -- [x] ensureValidSwimlaneIds migration -- [x] Fix cards without swimlaneId -- [x] Fix lists without swimlaneId -- [x] Rescue orphaned cards -- [x] Add validation hooks -- [x] Track migration status -- [x] Auto-run on startup - -### Error Handling -- [x] Fixed Migrations.findOne error -- [x] Fixed UserPositionHistory references -- [x] Added defensive checks -- [x] Proper error logging - ---- - -## 🧪 Testing Status - -### Unit Tests Status -- [ ] localStorageValidator.js - Not yet created -- [ ] userStorageHelpers.js - Not yet created -- [ ] userPositionHistory.js - Not yet created -- [ ] ensureValidSwimlaneIds.js - Not yet created - -### Integration Tests Status -- [ ] Card move tracking -- [ ] Undo/redo functionality -- [ ] Checkpoint restore -- [ ] localStorage cleanup -- [ ] SwimlaneId rescue - -### Manual Testing -- [ ] App starts without errors -- [ ] Collapse state persists per-user -- [ ] localStorage data is validated -- [ ] Orphaned cards are rescued -- [ ] Position history is created - ---- - -## 📚 Documentation Created - -- [x] PERSISTENCE_AUDIT.md - Complete system audit -- [x] ARCHITECTURE_IMPROVEMENTS.md - Implementation guide -- [x] IMPLEMENTATION_SUMMARY.md - This summary - ---- - -## 🚀 Deployment Readiness - -### Pre-Deployment -- [x] All code fixes applied -- [x] Migration system ready -- [x] Error handling in place -- [x] Backward compatibility maintained -- [ ] Unit tests created (TODO) -- [ ] Integration tests created (TODO) - -### Deployment -- [ ] Run on staging environment -- [ ] Verify no startup errors -- [ ] Check migration completion -- [ ] Test per-user settings persistence -- [ ] Validate undo/redo functionality - -### Post-Deployment -- [ ] Monitor for errors -- [ ] Verify data integrity -- [ ] Check localStorage cleanup -- [ ] Confirm no data loss - ---- - -## 📊 Metrics & Performance - -### Storage Limits -- LocalStorage max: 50 boards × 100 items = 5000 entries max -- UserPositionHistory: 1000 entries per user per board (checkpoints preserved) -- Auto-cleanup: Daily check for excess data - -### Query Performance -- Indexes created for fast retrieval -- Queries limited to 100 results -- Pagination support for history - -### Data Validation -- All reads: validated before use -- All writes: validated before storage -- Invalid data: silently removed - ---- - -## 🔐 Security Checklist - -- [x] User isolation in UserPositionHistory -- [x] UserID filtering on all queries -- [x] Type validation on all inputs -- [x] Bounds checking on numeric values -- [x] Board membership verification -- [x] Cannot modify other users' history -- [x] Checkpoints are per-user - ---- - -## 🎯 Feature Status - -### Completed ✅ -1. Per-user collapse state management -2. Per-user list width management -3. Per-user swimlane height management -4. localStorage validation and cleanup -5. Position history tracking -6. Undo/redo capability -7. Checkpoint/savepoint system -8. SwimlaneId validation and rescue - -### In Progress 🔄 -- UI components for undo/redo buttons -- History sidebar visualization - -### Planned 📋 -- Keyboard shortcuts (Ctrl+Z, Ctrl+Shift+Z) -- Field-level history for board data -- Search across historical values -- Visual timeline of changes - ---- - -## 📝 Code Quality - -### Documentation -- [x] Comments in all modified files -- [x] JSDoc comments for new functions -- [x] README in ARCHITECTURE_IMPROVEMENTS.md -- [x] Usage examples in IMPLEMENTATION_SUMMARY.md - -### Code Style -- [x] Consistent with Wekan codebase -- [x] Follows Meteor conventions -- [x] Error handling throughout -- [x] Defensive programming practices - -### Backward Compatibility -- [x] No breaking changes -- [x] Existing data preserved -- [x] Migration handles all edge cases -- [x] Fallback to defaults when needed - ---- - -## 🔧 Troubleshooting - -### Common Issues & Fixes - -| Issue | Cause | Fix | -|-------|-------|-----| -| "Migrations.findOne is not a function" | Collection not defined | ✅ Fixed - moved to top | -| UserPositionHistory not found | ES6 export in Meteor | ✅ Fixed - use globals | -| ChecklistItems undefined | Conditional reference | ✅ Fixed - added typeof check | -| localStorage quota exceeded | Too much data | ✅ Fixed - auto-cleanup | -| Collapsed state not persisting | Board-level vs per-user | ✅ Fixed - removed board-level | - ---- - -## 📞 Support - -### For Developers -- See ARCHITECTURE_IMPROVEMENTS.md for detailed implementation -- See PERSISTENCE_AUDIT.md for system audit -- Check inline code comments for specific logic - -### For Users -- Per-user settings are isolated and persistent -- Undo/redo coming in future releases -- Data is automatically cleaned up and validated - ---- - -## ✨ Summary - -**All critical issues have been resolved:** -1. ✅ Board-level UI state eliminated -2. ✅ Data validation fully implemented -3. ✅ Per-user position history created -4. ✅ SwimlaneId validation enforced -5. ✅ All startup errors fixed - -**The system is ready for:** -- Production deployment -- Further UI development -- Feature expansion - -**Next priorities:** -1. Create unit tests -2. Implement UI components -3. Add keyboard shortcuts -4. Expand to field-level history - ---- - -**Last Updated**: 2025-12-23 -**Status**: ✅ COMPLETE AND READY - diff --git a/docs/Security/PerUserDataAudit2025-12-23/IMPLEMENTATION_GUIDE.md b/docs/Security/PerUserDataAudit2025-12-23/IMPLEMENTATION_GUIDE.md deleted file mode 100644 index e2f0e81ca..000000000 --- a/docs/Security/PerUserDataAudit2025-12-23/IMPLEMENTATION_GUIDE.md +++ /dev/null @@ -1,451 +0,0 @@ -# Implementation Guide - Per-Board vs Per-User Data Storage - -**Status**: ✅ Complete -**Updated**: 2025-12-23 -**Scope**: Changes to implement per-board height/width storage and per-user-only collapse/label visibility - ---- - -## Overview of Changes - -This document details all changes required to properly separate per-board data from per-user data. - ---- - -## 1. Schema Changes ✅ COMPLETED - -### Swimlanes (swimlanes.js) ✅ -**Change**: Add `height` field to schema - -```javascript -// ADDED: -height: { - /** - * The height of the swimlane in pixels. - * -1 = auto-height (default) - * 50-2000 = fixed height in pixels - */ - type: Number, - optional: true, - defaultValue: -1, - custom() { - const h = this.value; - if (h !== -1 && (h < 50 || h > 2000)) { - return 'heightOutOfRange'; - } - }, -}, -``` - -**Status**: ✅ Implemented - -### Lists (lists.js) ✅ -**Change**: Add `width` field to schema - -```javascript -// ADDED: -width: { - /** - * The width of the list in pixels (100-1000). - * Default width is 272 pixels. - */ - type: Number, - optional: true, - defaultValue: 272, - custom() { - const w = this.value; - if (w < 100 || w > 1000) { - return 'widthOutOfRange'; - } - }, -}, -``` - -**Status**: ✅ Implemented - -### Cards (cards.js) ✅ -**Current**: Already has per-board `sort` field -**No Change Needed**: Positions stored in card.sort (per-board) - -**Status**: ✅ Already Correct - -### Checklists (checklists.js) ✅ -**Current**: Already has per-board `sort` field -**No Change Needed**: Positions stored in checklist.sort (per-board) - -**Status**: ✅ Already Correct - -### ChecklistItems (checklistItems.js) ✅ -**Current**: Already has per-board `sort` field -**No Change Needed**: Positions stored in checklistItem.sort (per-board) - -**Status**: ✅ Already Correct - ---- - -## 2. User Model Changes - -### Users (users.js) - Remove Per-User Width/Height Storage - -**Current Code Problem**: -- User profile stores `listWidths` (per-user) → should be per-board -- User profile stores `swimlaneHeights` (per-user) → should be per-board -- These methods access user.profile.listWidths and user.profile.swimlaneHeights - -**Solution**: Refactor these methods to read from list/swimlane documents instead - -#### Option A: Create Migration Helper (Recommended) - -Create a new file: `models/lib/persistenceHelpers.js` - -```javascript -// Get swimlane height from swimlane document (per-board storage) -export const getSwimlaneHeight = (swimlaneId) => { - const swimlane = Swimlanes.findOne(swimlaneId); - return swimlane && swimlane.height !== undefined ? swimlane.height : -1; -}; - -// Get list width from list document (per-board storage) -export const getListWidth = (listId) => { - const list = Lists.findOne(listId); - return list && list.width !== undefined ? list.width : 272; -}; - -// Set swimlane height in swimlane document (per-board storage) -export const setSwimlaneHeight = (swimlaneId, height) => { - if (height !== -1 && (height < 50 || height > 2000)) { - throw new Error('Height out of range: -1 or 50-2000'); - } - Swimlanes.update(swimlaneId, { $set: { height } }); -}; - -// Set list width in list document (per-board storage) -export const setListWidth = (listId, width) => { - if (width < 100 || width > 1000) { - throw new Error('Width out of range: 100-1000'); - } - Lists.update(listId, { $set: { width } }); -}; -``` - -#### Option B: Modify User Methods - -**Change these methods in users.js**: - -1. **getListWidth(boardId, listId)** - Remove per-user lookup - ```javascript - // OLD (removes this): - // const listWidths = this.getListWidths(); - // if (listWidths[boardId] && listWidths[boardId][listId]) { - // return listWidths[boardId][listId]; - // } - - // NEW: - getListWidth(listId) { - const list = ReactiveCache.getList({ _id: listId }); - return list && list.width ? list.width : 272; - }, - ``` - -2. **getSwimlaneHeight(boardId, swimlaneId)** - Remove per-user lookup - ```javascript - // OLD (removes this): - // const swimlaneHeights = this.getSwimlaneHeights(); - // if (swimlaneHeights[boardId] && swimlaneHeights[boardId][swimlaneId]) { - // return swimlaneHeights[boardId][swimlaneId]; - // } - - // NEW: - getSwimlaneHeight(swimlaneId) { - const swimlane = ReactiveCache.getSwimlane(swimlaneId); - return swimlane && swimlane.height ? swimlane.height : -1; - }, - ``` - -3. **setListWidth(boardId, listId, width)** - Update list document - ```javascript - // OLD (removes this): - // let currentWidths = this.getListWidths(); - // if (!currentWidths[boardId]) { - // currentWidths[boardId] = {}; - // } - // currentWidths[boardId][listId] = width; - - // NEW: - setListWidth(listId, width) { - Lists.update(listId, { $set: { width } }); - }, - ``` - -4. **setSwimlaneHeight(boardId, swimlaneId, height)** - Update swimlane document - ```javascript - // OLD (removes this): - // let currentHeights = this.getSwimlaneHeights(); - // if (!currentHeights[boardId]) { - // currentHeights[boardId] = {}; - // } - // currentHeights[boardId][swimlaneId] = height; - - // NEW: - setSwimlaneHeight(swimlaneId, height) { - Swimlanes.update(swimlaneId, { $set: { height } }); - }, - ``` - -### Keep These Per-User Storage Methods - -These should remain in user profile (per-user only): - -1. **Collapse Swimlanes** (per-user) - ```javascript - getCollapsedSwimlanes() { - const { collapsedSwimlanes = {} } = this.profile || {}; - return collapsedSwimlanes; - }, - setCollapsedSwimlane(boardId, swimlaneId, collapsed) { - // ... update user.profile.collapsedSwimlanes[boardId][swimlaneId] - }, - isCollapsedSwimlane(boardId, swimlaneId) { - // ... check user.profile.collapsedSwimlanes - }, - ``` - -2. **Collapse Lists** (per-user) - ```javascript - getCollapsedLists() { - const { collapsedLists = {} } = this.profile || {}; - return collapsedLists; - }, - setCollapsedList(boardId, listId, collapsed) { - // ... update user.profile.collapsedLists[boardId][listId] - }, - isCollapsedList(boardId, listId) { - // ... check user.profile.collapsedLists - }, - ``` - -3. **Hide Minicard Label Text** (per-user) - ```javascript - getHideMiniCardLabelText(boardId) { - const { hideMiniCardLabelText = {} } = this.profile || {}; - return hideMiniCardLabelText[boardId] || false; - }, - setHideMiniCardLabelText(boardId, hidden) { - // ... update user.profile.hideMiniCardLabelText[boardId] - }, - ``` - -### Remove From User Schema - -These fields should be removed from user.profile schema in users.js: - -```javascript -// REMOVE from schema: -'profile.listWidths': { ... }, // Now stored in list.width -'profile.swimlaneHeights': { ... }, // Now stored in swimlane.height -``` - ---- - -## 3. Client-Side Changes - -### Storage Access Layer - -When UI needs to get/set widths and heights: - -**OLD APPROACH** (removes this): -```javascript -// Getting from user profile -const width = Meteor.user().getListWidth(boardId, listId); - -// Setting to user profile -Meteor.call('setListWidth', boardId, listId, 300); -``` - -**NEW APPROACH**: -```javascript -// Getting from list document -const width = Lists.findOne(listId)?.width || 272; - -// Setting to list document -Lists.update(listId, { $set: { width: 300 } }); -``` - -### Meteor Methods to Remove - -Remove these Meteor methods that updated user profile: - -```javascript -// Remove: -Meteor.methods({ - 'setListWidth': function(boardId, listId, width) { ... }, - 'setSwimlaneHeight': function(boardId, swimlaneId, height) { ... }, -}); -``` - ---- - -## 4. Migration Script - -Create file: `server/migrations/migrateToPerBoardStorage.js` - -```javascript -const MIGRATION_NAME = 'migrate-to-per-board-height-width-storage'; - -Migrations = new Mongo.Collection('migrations'); - -Meteor.startup(() => { - const existingMigration = Migrations.findOne({ name: MIGRATION_NAME }); - - if (!existingMigration) { - try { - // Migrate swimlane heights from user.profile to swimlane.height - Meteor.users.find().forEach(user => { - const swimlaneHeights = user.profile?.swimlaneHeights || {}; - - Object.keys(swimlaneHeights).forEach(boardId => { - Object.keys(swimlaneHeights[boardId]).forEach(swimlaneId => { - const height = swimlaneHeights[boardId][swimlaneId]; - - // Validate height - if (height === -1 || (height >= 50 && height <= 2000)) { - Swimlanes.update( - { _id: swimlaneId, boardId }, - { $set: { height } }, - { multi: false } - ); - } - }); - }); - }); - - // Migrate list widths from user.profile to list.width - Meteor.users.find().forEach(user => { - const listWidths = user.profile?.listWidths || {}; - - Object.keys(listWidths).forEach(boardId => { - Object.keys(listWidths[boardId]).forEach(listId => { - const width = listWidths[boardId][listId]; - - // Validate width - if (width >= 100 && width <= 1000) { - Lists.update( - { _id: listId, boardId }, - { $set: { width } }, - { multi: false } - ); - } - }); - }); - }); - - // Record successful migration - Migrations.insert({ - name: MIGRATION_NAME, - status: 'completed', - createdAt: new Date(), - migratedSwimlanes: Swimlanes.find({ height: { $exists: true, $ne: -1 } }).count(), - migratedLists: Lists.find({ width: { $exists: true, $ne: 272 } }).count(), - }); - - console.log('✅ Migration to per-board height/width storage completed'); - - } catch (error) { - console.error('❌ Migration failed:', error); - Migrations.insert({ - name: MIGRATION_NAME, - status: 'failed', - error: error.message, - createdAt: new Date(), - }); - } - } -}); -``` - ---- - -## 5. Testing Checklist - -### Schema Testing -- [ ] Swimlane with height = -1 accepts insert -- [ ] Swimlane with height = 100 accepts insert -- [ ] Swimlane with height = 25 rejects (< 50) -- [ ] Swimlane with height = 3000 rejects (> 2000) -- [ ] List with width = 272 accepts insert -- [ ] List with width = 50 rejects (< 100) -- [ ] List with width = 2000 rejects (> 1000) - -### Data Persistence Testing -- [ ] Resize swimlane → height saved in swimlane document -- [ ] Reload page → swimlane height persists -- [ ] Different user loads page → sees same height -- [ ] Resize list → width saved in list document -- [ ] Reload page → list width persists -- [ ] Different user loads page → sees same width - -### Per-User Testing -- [ ] User A collapses swimlane → User B sees it expanded -- [ ] User A hides labels → User B sees labels -- [ ] Reload page → per-user preferences persist for same user -- [ ] Different user logs in → doesn't see previous user's preferences - -### Migration Testing -- [ ] Run migration on database with old per-user data -- [ ] All swimlane heights migrated to swimlane documents -- [ ] All list widths migrated to list documents -- [ ] User.profile.swimlaneHeights can be safely removed -- [ ] User.profile.listWidths can be safely removed - ---- - -## 6. Rollback Plan - -If issues occur: - -1. **Before Migration**: Backup MongoDB - ```bash - mongodump -d wekan -o backup-wekan-before-migration - ``` - -2. **If Needed**: Restore from backup - ```bash - mongorestore -d wekan backup-wekan-before-migration/wekan - ``` - -3. **Revert Code**: Restore previous swimlanes.js, lists.js, users.js - ---- - -## 7. Files Modified - -| File | Change | Status | -|------|--------|--------| -| [models/swimlanes.js](../../../models/swimlanes.js) | Add height field | ✅ Done | -| [models/lists.js](../../../models/lists.js) | Add width field | ✅ Done | -| [models/users.js](../../../models/users.js) | Refactor height/width methods | ⏳ TODO | -| server/migrations/migrateToPerBoardStorage.js | Migration script | ⏳ TODO | -| [docs/Security/PerUserDataAudit2025-12-23/DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md) | Architecture docs | ✅ Done | - ---- - -## 8. Summary of Per-User vs Per-Board Data - -### ✅ Per-Board Data (All Users See Same Value) -- Swimlane height -- List width -- Card position (sort) -- Checklist position (sort) -- ChecklistItem position (sort) -- All titles, colors, descriptions - -### 🔒 Per-User Data (Only That User Sees Their Value) -- Collapse state (swimlane, list, card) -- Hide minicard label text visibility -- Stored in user.profile or cookie - ---- - -**Status**: ✅ Architecture and schema changes complete -**Next**: Refactor user methods and run migration - diff --git a/docs/Security/PerUserDataAudit2025-12-23/IMPLEMENTATION_SUMMARY.md b/docs/Security/PerUserDataAudit2025-12-23/IMPLEMENTATION_SUMMARY.md deleted file mode 100644 index 3baaa50d9..000000000 --- a/docs/Security/PerUserDataAudit2025-12-23/IMPLEMENTATION_SUMMARY.md +++ /dev/null @@ -1,337 +0,0 @@ -# Wekan Architecture Improvements - Implementation Summary - -## Status: ✅ Complete and Ready for Testing - -All architectural improvements have been successfully implemented and fixed. The application should now start without errors. - ---- - -## Files Created - -### 1. LocalStorage Validation System -- **[client/lib/localStorageValidator.js](client/lib/localStorageValidator.js)** - - Validates all localStorage data for per-user UI preferences - - Auto-cleanup of invalid/corrupted data - - Runs on app startup (once per day) - - Exported functions for use by other modules - -### 2. User Storage Helpers -- **[models/lib/userStorageHelpers.js](models/lib/userStorageHelpers.js)** - - Helper functions for validated get/set operations - - Type checking and bounds validation - - Used by users model for localStorage operations - -### 3. Per-User Position History -- **[models/userPositionHistory.js](models/userPositionHistory.js)** - - New Mongo collection for tracking entity movements - - Per-user history isolation - - Undo/redo capabilities - - Checkpoint/savepoint system - - Meteor methods for client interaction - -### 4. SwimlaneId Validation Migration -- **[server/migrations/ensureValidSwimlaneIds.js](server/migrations/ensureValidSwimlaneIds.js)** - - Automatic migration on server startup - - Ensures all cards have valid swimlaneId - - Rescues orphaned data to "Rescued Data" swimlane - - Adds validation hooks to prevent swimlaneId removal - ---- - -## Files Modified - -### 1. Swimlane Schema -- **[models/swimlanes.js](models/swimlanes.js)** - - ❌ Removed `collapsed` field (board-level) - - ❌ Removed `collapse()` mutation - - ✅ Added comments explaining per-user storage - -### 2. List Schema -- **[models/lists.js](models/lists.js)** - - ❌ Removed `collapsed` field (board-level) - - ❌ Removed REST API collapsed field handling - - ✅ Added comments explaining per-user storage - -### 3. Cards Model -- **[models/cards.js](models/cards.js)** - - ✅ Enhanced `move()` method to track changes - - ✅ Automatic UserPositionHistory entry creation - - ✅ Defensive checks for UserPositionHistory existence - -### 4. User Model -- **[models/users.js](models/users.js)** - - Updated to use validated localStorage functions - - Enhanced validation for list widths and swimlane heights - - Type checking on all values - ---- - -## Features Implemented - -### ✅ Completed Features - -1. **Per-User UI State Management** - - Collapse states (swimlanes, lists) - per-user only - - List widths - per-board, per-user - - Swimlane heights - per-board, per-user - - Stored in user profile (logged-in) and localStorage (non-logged-in) - -2. **Data Validation** - - All localStorage data validated on read/write - - Invalid data automatically removed - - Numeric ranges enforced: - - List widths: 100-1000 pixels - - Swimlane heights: -1 (auto) or 50-2000 pixels - - Corrupted data cleaned up automatically - -3. **Position History Tracking** - - Automatic tracking of card movements - - Per-user isolation (users see only their own history) - - Full undo/redo capability - - Checkpoint/savepoint system for marking important states - - Batch operation support for grouping related changes - -4. **SwimlaneId Validation** - - All cards assigned valid swimlaneId - - Orphaned data rescued to special swimlane - - Validation hooks prevent swimlaneId removal - - Automatic on server startup - -### ⏳ Planned Features (for future implementation) - -- UI components for undo/redo buttons -- History sidebar visualization -- Keyboard shortcuts (Ctrl+Z, Ctrl+Shift+Z) -- Field-level history for board data -- Search across historical values - ---- - -## Bug Fixes Applied - -1. **Fixed Migrations Collection Issue** - - Moved collection definition to top of file - - Ensured it's available before use - - Fixed startup error: "Migrations.findOne is not a function" - -2. **Fixed UserPositionHistory References** - - Removed ES6 export (Meteor uses globals) - - Added defensive checks for collection existence - - Fixed ChecklistItems reference - -3. **Fixed LocalStorage Validator** - - Proper client-side guard - - Conditional Meteor.startup() call - ---- - -## Migration Information - -### Automatic Migrations - -1. **ensureValidSwimlaneIds** (v1) - - Runs automatically on server startup - - No manual action required - - Tracks completion in `migrations` collection - -### Data Changes - -- Existing `collapsed` field values in swimlanes/lists are ignored -- Per-user collapse states take precedence -- Card swimlaneId is auto-assigned if missing -- Orphaned cards moved to rescue swimlane - ---- - -## Testing Instructions - -### Manual Verification - -1. **Start the application** - ```bash - cd /home/wekan/repos/wekan - npm start - ``` - -2. **Check for startup errors** - - Should not see "Migrations.findOne is not a function" - - Should see migration completion logs - - Should see validation hook installation - -3. **Test Per-User Settings** - - Collapse a swimlane → Log out → Login as different user - - Swimlane should be expanded for the other user - - Previous user's collapse state restored when logged back in - -4. **Test Data Validation** - - Corrupt localStorage data - - Restart app - - Data should be cleaned up automatically - -5. **Test Position History** - - Move a card between lists - - Check that history entry was created - - Verify undo capability - -### Automated Testing (Todo) - -- [ ] Unit tests for localStorageValidator -- [ ] Unit tests for userPositionHistory -- [ ] Integration tests for card move tracking -- [ ] Migration tests for swimlaneId fixing - ---- - -## Database Indexes - -New indexes created for performance: - -```javascript -UserPositionHistory: -- { userId: 1, boardId: 1, createdAt: -1 } -- { userId: 1, entityType: 1, entityId: 1 } -- { userId: 1, isCheckpoint: 1 } -- { batchId: 1 } -- { createdAt: 1 } -``` - ---- - -## API Methods Added - -### Meteor Methods - -```javascript -Meteor.methods({ - 'userPositionHistory.createCheckpoint'(boardId, checkpointName) - 'userPositionHistory.undo'(historyId) - 'userPositionHistory.getRecent'(boardId, limit) - 'userPositionHistory.getCheckpoints'(boardId) - 'userPositionHistory.restoreToCheckpoint'(checkpointId) -}); -``` - ---- - -## Performance Considerations - -1. **LocalStorage Limits** - - Max 50 boards per key - - Max 100 items per board - - Excess data removed during daily cleanup - -2. **Position History Limits** - - Max 1000 entries per user per board - - Checkpoints never deleted - - Old entries auto-deleted - -3. **Query Optimization** - - Limited to 100 results maximum - - Proper indexes for fast retrieval - - Auto-cleanup prevents unbounded growth - ---- - -## Security Notes - -1. **User Isolation** - - UserPositionHistory filtered by userId - - Users can only undo their own changes - - Checkpoints are per-user - -2. **Data Validation** - - All inputs validated before storage - - Invalid data rejected, not sanitized - - Type checking enforced - -3. **Authorization** - - Board membership verified - - Meteor.userId() required for history operations - - Cannot modify other users' history - ---- - -## Backward Compatibility - -✅ **All changes are backward compatible:** -- Existing board-level `collapsed` fields are ignored -- Per-user settings take precedence -- Migration handles orphaned data gracefully -- No data loss - ---- - -## Next Steps - -1. **Testing** - - Run manual tests (see Testing Instructions) - - Verify no startup errors - - Check position history tracking - -2. **UI Implementation** (Future) - - Create undo/redo buttons - - Implement history sidebar - - Add keyboard shortcuts - -3. **Feature Expansion** (Future) - - Add field-level history - - Implement search across history - - Add visual timeline - ---- - -## Documentation References - -- [PERSISTENCE_AUDIT.md](PERSISTENCE_AUDIT.md) - Complete system audit -- [ARCHITECTURE_IMPROVEMENTS.md](ARCHITECTURE_IMPROVEMENTS.md) - Detailed implementation guide - ---- - -## Files Summary - -| File | Type | Status | Purpose | -|------|------|--------|---------| -| client/lib/localStorageValidator.js | New | ✅ Complete | Validate and cleanup localStorage | -| models/lib/userStorageHelpers.js | New | ✅ Complete | Helper functions for storage | -| models/userPositionHistory.js | New | ✅ Complete | Per-user position history | -| server/migrations/ensureValidSwimlaneIds.js | New | ✅ Complete | Validate swimlaneIds | -| models/swimlanes.js | Modified | ✅ Complete | Removed board-level collapse | -| models/lists.js | Modified | ✅ Complete | Removed board-level collapse | -| models/cards.js | Modified | ✅ Complete | Added position tracking | -| models/users.js | Modified | ✅ Complete | Enhanced storage validation | - ---- - -## Known Limitations - -1. **Undo/Redo UI** - Not yet implemented (planned for future) -2. **Field History** - Only position history tracked (future feature) -3. **Collaborative Undo** - Single-user undo only for now -4. **Search History** - Not yet implemented - ---- - -## Support & Troubleshooting - -### If app won't start: -1. Check MongoDB is running: `ps aux | grep mongod` -2. Check logs for specific error messages -3. Verify collection definitions are loaded -4. Check for typos in model files - -### If data is missing: -1. Check `migrations` collection for completion status -2. Look for orphaned data in "Rescued Data" swimlane -3. Verify localStorage wasn't cleared - -### If undo doesn't work: -1. Verify UserPositionHistory collection exists -2. Check that history entries were created -3. Ensure entity still exists (deleted entities cannot be undone) - ---- - -**Status**: Ready for production deployment -**Last Updated**: 2025-12-23 -**Version**: 1.0 - diff --git a/docs/Security/PerUserDataAudit2025-12-23/PERSISTENCE_AUDIT.md b/docs/Security/PerUserDataAudit2025-12-23/PERSISTENCE_AUDIT.md deleted file mode 100644 index 24446657d..000000000 --- a/docs/Security/PerUserDataAudit2025-12-23/PERSISTENCE_AUDIT.md +++ /dev/null @@ -1,472 +0,0 @@ -# Wekan Persistence Audit Report - -## Overview -This document audits the persistence mechanisms for Wekan board data, including swimlanes, lists, cards, checklists, and their properties (order, color, background, titles, etc.), as well as per-user settings. - ---- - -## 1. BOARD-LEVEL PERSISTENCE (Persisted Across All Users) - -### 1.1 Swimlanes - -**Collection**: `swimlanes` ([models/swimlanes.js](models/swimlanes.js)) - -**Persisted Fields**: -- ✅ `title` - Swimlane title (via `rename()` mutation) -- ✅ `sort` - Swimlane ordering/position (decimal number) -- ✅ `color` - Swimlane color (via `setColor()` mutation) -- ✅ `collapsed` - Swimlane collapsed state (via `collapse()` mutation) **⚠️ See note below** -- ✅ `archived` - Swimlane archived status - -**Persistence Mechanism**: -- Direct MongoDB updates via `Swimlanes.update()` and `Swimlanes.direct.update()` -- Automatic timestamps: `updatedAt`, `modifiedAt` fields -- Activity tracking for title changes and archive/restore operations - -**Issues Found**: -- ⚠️ **ISSUE**: `collapsed` field in swimlanes.js line 127 is set to `defaultValue: false` but the `isCollapsed()` helper (line 251-263) checks for per-user stored values. This creates a mismatch between board-level and per-user storage. - ---- - -### 1.2 Lists - -**Collection**: `lists` ([models/lists.js](models/lists.js)) - -**Persisted Fields**: -- ✅ `title` - List title -- ✅ `sort` - List ordering/position (decimal number) -- ✅ `color` - List color -- ✅ `collapsed` - List collapsed state (board-wide via REST API line 768-775) **⚠️ See note below** -- ✅ `starred` - List starred status -- ✅ `wipLimit` - WIP limit configuration -- ✅ `archived` - List archived status - -**Persistence Mechanism**: -- Direct MongoDB updates via `Lists.update()` and `Lists.direct.update()` -- Automatic timestamps: `updatedAt`, `modifiedAt` -- Activity tracking for title changes, archive/restore - -**Issues Found**: -- ⚠️ **ISSUE**: Similar to swimlanes, `collapsed` field (line 147) defaults to `false` but the `isCollapsed()` helper (line 303-311) also checks for per-user stored values. The REST API allows board-level collapsed state updates (line 768-775), but client also stores per-user via `getCollapsedListFromStorage()`. -- ⚠️ **ISSUE**: The `swimlaneId` field is part of the list (line 48), but `draggableLists()` method (line 275) filters by board only, suggesting lists are shared across swimlanes rather than per-swimlane. - ---- - -### 1.3 Cards - -**Collection**: `cards` ([models/cards.js](models/cards.js)) - -**Persisted Fields**: -- ✅ `title` - Card title -- ✅ `sort` - Card ordering/position within list -- ✅ `color` - Card color (via `setColor()` mutation, line 2268) -- ✅ `boardId`, `swimlaneId`, `listId` - Card location -- ✅ `archived` - Card archived status -- ✅ `description` - Card description -- ✅ Custom fields, labels, members, assignees, etc. - -**Persistence Mechanism**: -- `move()` method (line 2063+) handles reordering and moving cards across swimlanes/lists/boards -- Automatic timestamp updates via `modifiedAt`, `dateLastActivity` -- Activity tracking for moves, title changes, etc. -- Attachment metadata updated alongside card moves (line 2101-2115) - -**Issues Found**: -- ✅ **OK**: Order/sort persistence working correctly via card.move() and card.moveOptionalArgs() -- ✅ **OK**: Color persistence working correctly -- ✅ **OK**: Title changes persisted automatically - ---- - -### 1.4 Checklists - -**Collection**: `checklists` ([models/checklists.js](models/checklists.js)) - -**Persisted Fields**: -- ✅ `title` - Checklist title (via `setTitle()` mutation) -- ✅ `sort` - Checklist ordering (decimal number) -- ✅ `hideCheckedChecklistItems` - Toggle for hiding checked items -- ✅ `hideAllChecklistItems` - Toggle for hiding all items - -**Persistence Mechanism**: -- Direct MongoDB updates via `Checklists.update()` -- Automatic timestamps: `createdAt`, `modifiedAt` -- Activity tracking for creation and removal - ---- - -### 1.5 Checklist Items - -**Collection**: `checklistItems` ([models/checklistItems.js](models/checklistItems.js)) - -**Persisted Fields**: -- ✅ `title` - Item text (via `setTitle()` mutation) -- ✅ `sort` - Item ordering within checklist (decimal number) -- ✅ `isFinished` - Item completion status (via `check()`, `uncheck()`, `toggleItem()` mutations) - -**Persistence Mechanism**: -- `move()` mutation (line 159-168) handles reordering within checklists -- Direct MongoDB updates via `ChecklistItems.update()` -- Automatic timestamps: `createdAt`, `modifiedAt` -- Activity tracking for item creation/removal and completion state changes - -**Issue Found**: -- ✅ **OK**: Item order and completion status persist correctly - ---- - -### 1.6 Position History Tracking - -**Collection**: `positionHistory` ([models/positionHistory.js](models/positionHistory.js)) - -**Purpose**: Tracks original positions of swimlanes, lists, and cards before changes - -**Features**: -- ✅ Stores original `sort` position -- ✅ Stores original titles -- ✅ Supports swimlanes, lists, and cards -- ✅ Provides helpers to check if entity moved from original position - -**Implementation Notes**: -- Swimlanes track position automatically on insert (swimlanes.js line 387-393) -- Lists track position automatically on insert (lists.js line 487+) -- Can detect moves via `hasMoved()` and `hasMovedFromOriginalPosition()` helpers - ---- - -## 2. PER-USER SETTINGS (NOT Persisted Across Boards) - -### 2.1 Per-Board, Per-User Settings - -**Storage**: User `profile` subdocuments ([models/users.js](models/users.js)) - -#### A. List Widths -- **Field**: `profile.listWidths` (line 527) -- **Structure**: `listWidths[boardId][listId] = width` -- **Persistence**: Via `setListWidth()` mutation (line 1834) -- **Retrieval**: `getListWidth()`, `getListWidthFromStorage()` (line 1288-1313) -- **Constraints**: Also stored in `profile.listConstraints` -- ✅ **Status**: Working correctly - -#### B. Swimlane Heights -- **Field**: `profile.swimlaneHeights` (searchable in line 1047+) -- **Structure**: `swimlaneHeights[boardId][swimlaneId] = height` -- **Persistence**: Via `setSwimlaneHeight()` mutation (line 1878) -- **Retrieval**: `getSwimlaneHeight()`, `getSwimlaneHeightFromStorage()` (line 1050-1080) -- ✅ **Status**: Working correctly - -#### C. Collapsed Swimlanes (Per-User) -- **Field**: `profile.collapsedSwimlanes` (line 1900) -- **Structure**: `collapsedSwimlanes[boardId][swimlaneId] = boolean` -- **Persistence**: Via `setCollapsedSwimlane()` mutation (line 1900-1906) -- **Retrieval**: `getCollapsedSwimlaneFromStorage()` (swimlanes.js line 251-263) -- **Client-Side Fallback**: `Users.getPublicCollapsedSwimlane()` for public/non-logged-in users (users.js line 60-73) -- ✅ **Status**: Working correctly for logged-in users - -#### D. Collapsed Lists (Per-User) -- **Field**: `profile.collapsedLists` (line 1893) -- **Structure**: `collapsedLists[boardId][listId] = boolean` -- **Persistence**: Via `setCollapsedList()` mutation (line 1893-1899) -- **Retrieval**: `getCollapsedListFromStorage()` (lists.js line 303-311) -- **Client-Side Fallback**: `Users.getPublicCollapsedList()` for public users (users.js line 44-52) -- ✅ **Status**: Working correctly for logged-in users - -#### E. Card Collapsed State (Global Per-User) -- **Field**: `profile.cardCollapsed` (line 267) -- **Persistence**: Via `setCardCollapsed()` method (line 2088-2091) -- **Retrieval**: `cardCollapsed()` helper in cardDetails.js (line 100-107) -- **Client-Side Fallback**: `Users.getPublicCardCollapsed()` for public users (users.js line 80-85) -- ✅ **Status**: Working correctly (applies to all boards for a user) - -#### F. Card Maximized State (Global Per-User) -- **Field**: `profile.cardMaximized` (line 260) -- **Persistence**: Via `toggleCardMaximized()` mutation (line 1720-1726) -- **Retrieval**: `hasCardMaximized()` helper (line 1194-1196) -- ✅ **Status**: Working correctly - -#### G. Board Workspace Trees (Global Per-User) -- **Field**: `profile.boardWorkspacesTree` (line 1981-2026) -- **Purpose**: Stores nested workspace structure for organizing boards -- **Persistence**: Via `setWorkspacesTree()` method (line 1995-2000) -- ✅ **Status**: Working correctly - -#### H. Board Workspace Assignments (Global Per-User) -- **Field**: `profile.boardWorkspaceAssignments` (line 2002-2011) -- **Purpose**: Maps each board to a workspace ID -- **Persistence**: Via `assignBoardToWorkspace()` and `unassignBoardFromWorkspace()` methods -- ✅ **Status**: Working correctly - -#### I. All Boards Workspaces Setting -- **Field**: `profile.boardView` (line 1807) -- **Persistence**: Via `setBoardView()` method (line 1805-1809) -- **Description**: Per-user preference for "All Boards" view style -- ✅ **Status**: Working correctly - ---- - -### 2.2 Client-Side Storage (Non-Logged-In Users) - -**Storage Methods**: -1. **Cookies** (via `readCookieMap()`/`writeCookieMap()`): - - `wekan-collapsed-lists` - Collapsed list states (users.js line 44-58) - - `wekan-collapsed-swimlanes` - Collapsed swimlane states - -2. **localStorage**: - - `wekan-list-widths` - List widths (getListWidthFromStorage, line 1316-1327) - - `wekan-swimlane-heights` - Swimlane heights (setSwimlaneHeightToStorage, line 1100-1123) - -**Coverage**: -- ✅ Collapse status for lists and swimlanes -- ✅ Width constraints for lists -- ✅ Height constraints for swimlanes -- ❌ Card collapsed state (only via cookies, fallback available) - ---- - -## 3. CRITICAL FINDINGS & ISSUES - -### 3.1 HIGH PRIORITY ISSUES - -#### Issue #1: Collapsed State Inconsistency (Swimlanes) -**Severity**: HIGH -**Location**: [models/swimlanes.js](models/swimlanes.js) lines 127, 251-263 - -**Problem**: -- The swimlane schema defines `collapsed` as a board-level field (defaults to false) -- But the `isCollapsed()` helper prioritizes per-user stored values from the user profile -- This creates confusion: is collapsed state board-wide or per-user? - -**Expected Behavior**: Per-user settings should be stored in `profile.collapsedSwimlanes`, not in the swimlane document itself. - -**Recommendation**: -```javascript -// CURRENT (WRONG): -collapsed: { - type: Boolean, - defaultValue: false, // Board-wide field -}, - -// SUGGESTED (CORRECT): -// Remove 'collapsed' from swimlane schema -// Only store per-user state in profile.collapsedSwimlanes -``` - ---- - -#### Issue #2: Collapsed State Inconsistency (Lists) -**Severity**: HIGH -**Location**: [models/lists.js](models/lists.js) lines 147, 303-311 - -**Problem**: -- Similar to swimlanes, lists have a board-level `collapsed` field -- REST API allows updating this field (line 768-775) -- But `isCollapsed()` helper checks per-user values first -- Migrations copy `collapsed` status between lists (fixMissingListsMigration.js line 165) - -**Recommendation**: Clarify whether collapsed state should be: -1. **Option A**: Board-level only (remove per-user override) -2. **Option B**: Per-user only (remove board-level field) -3. **Option C**: Hybrid with clear precedence rules - ---- - -#### Issue #3: Swimlane/List Organization Model Unclear -**Severity**: MEDIUM -**Location**: [models/lists.js](models/lists.js) lines 48, 201-230, 275 - -**Problem**: -- Lists have a `swimlaneId` field but `draggableLists()` filters by `boardId` only -- Some methods reference `myLists()` which filters by both `boardId` and `swimlaneId` -- Migrations suggest lists were transitioning from per-swimlane to shared-across-swimlane model - -**Questions**: -- Are lists shared across all swimlanes or isolated to each swimlane? -- What happens when dragging a list to a different swimlane? - -**Recommendation**: Document the intended architecture clearly. - ---- - -### 3.2 MEDIUM PRIORITY ISSUES - -#### Issue #4: Position History Only Tracks Original Position -**Severity**: MEDIUM -**Location**: [models/positionHistory.js](models/positionHistory.js) - -**Problem**: -- Position history tracks the *original* position when an entity is created -- It does NOT track subsequent moves/reorders -- Historical audit trail of all position changes is lost - -**Impact**: Cannot determine full history of where a card/list was located over time - -**Recommendation**: Consider extending to track all position changes with timestamps. - ---- - -#### Issue #5: Card Collapsed State is Global Per-User, Not Per-Card -**Severity**: LOW -**Location**: [models/users.js](models/users.js) line 267, users.js line 2088-2091 - -**Problem**: -- `profile.cardCollapsed` is a single boolean affecting all cards for a user -- It's not per-card or per-board, just a global toggle -- Name is misleading - -**Recommendation**: Consider renaming to `cardDetailsCollapsedByDefault` or similar. - ---- - -#### Issue #6: Public User Settings Storage Incomplete -**Severity**: MEDIUM -**Location**: [models/users.js](models/users.js) lines 44-85 - -**Problem**: -- Cookie-based storage for public users only covers: - - Collapsed lists - - Collapsed swimlanes -- Missing storage for: - - List widths - - Swimlane heights - - Card collapsed state - -**Impact**: Public/non-logged-in users lose UI preferences on page reload - -**Recommendation**: Implement localStorage storage for all per-user preferences. - ---- - -### 3.3 VERIFICATION CHECKLIST - -| Item | Status | Notes | -|------|--------|-------| -| Swimlane order persistence | ✅ | Via `sort` field, board-level | -| List order persistence | ✅ | Via `sort` field, board-level | -| Card order persistence | ✅ | Via `sort` field, card.move() | -| Checklist order persistence | ✅ | Via `sort` field | -| Checklist item order persistence | ✅ | Via `sort` field, ChecklistItems.move() | -| Swimlane color changes | ✅ | Via `setColor()` mutation | -| List color changes | ✅ | Via REST API or direct update | -| Card color changes | ✅ | Via `setColor()` mutation | -| Swimlane title changes | ✅ | Via `rename()` mutation, activity tracked | -| List title changes | ✅ | Via REST API or `rename()` mutation, activity tracked | -| Card title changes | ✅ | Via direct update, activity tracked | -| Checklist title changes | ✅ | Via `setTitle()` mutation | -| Checklist item title changes | ✅ | Via `setTitle()` mutation | -| Per-user list widths | ✅ | Via `profile.listWidths` | -| Per-user swimlane heights | ✅ | Via `profile.swimlaneHeights` | -| Per-user swimlane collapse state | ✅ | Via `profile.collapsedSwimlanes` | -| Per-user list collapse state | ✅ | Via `profile.collapsedLists` | -| Per-user card collapse state | ✅ | Via `profile.cardCollapsed` | -| Per-user board workspace organization | ✅ | Via `profile.boardWorkspacesTree` | -| Activity logging for changes | ✅ | Via Activities collection | - ---- - -## 4. RECOMMENDATIONS - -### 4.1 Immediate Actions - -1. **Clarify Collapsed State Architecture** - - Decide if collapsed state should be per-user or board-wide - - Update swimlanes.js and lists.js schema accordingly - - Update documentation - -2. **Complete Public User Storage** - - Implement localStorage for list widths/swimlane heights for non-logged-in users - - Test persistence across page reloads - -3. **Review Position History Usage** - - Confirm if current position history implementation meets requirements - - Consider extending to track all changes (not just original position) - -### 4.2 Long-Term Improvements - -1. **Audit Trail Feature** - - Extend position history to track all moves with timestamps - - Enable board managers to see complete history of card/list movements - -2. **Data Integrity Tests** - - Add integration tests to verify: - - Order is persisted correctly after drag-drop - - Color changes persist across sessions - - Per-user settings apply only to correct user - - Per-user settings don't leak across boards - -3. **Database Indexes** - - Verify indexes exist for common queries: - - `sort` fields for swimlanes, lists, cards, checklists - - `boardId` fields for filtering - -### 4.3 Code Quality Improvements - -1. **Document Persistence Model** - - Add clear comments explaining which fields are board-level vs. per-user - - Document swimlane/list relationship model - -2. **Consistent Naming** - - Rename misleading field names (e.g., `cardCollapsed`) - - Align method names with actual functionality - ---- - -## 5. SUMMARY TABLE - -### Board-Level Persistence (Shared Across Users) - -| Entity | Field | Type | Persisted | Notes | -|--------|-------|------|-----------|-------| -| Swimlane | title | Text | ✅ | Via rename() | -| Swimlane | sort | Number | ✅ | For ordering | -| Swimlane | color | String | ✅ | Via setColor() | -| Swimlane | collapsed | Boolean | ⚠️ | **Issue #1**: Conflicts with per-user storage | -| Swimlane | archived | Boolean | ✅ | Via archive()/restore() | -| | | | | | -| List | title | Text | ✅ | Via rename() or REST | -| List | sort | Number | ✅ | For ordering | -| List | color | String | ✅ | Via REST or update | -| List | collapsed | Boolean | ⚠️ | **Issue #2**: Conflicts with per-user storage | -| List | starred | Boolean | ✅ | Via REST or update | -| List | wipLimit | Object | ✅ | Via REST or setWipLimit() | -| List | archived | Boolean | ✅ | Via archive() | -| | | | | | -| Card | title | Text | ✅ | Direct update | -| Card | sort | Number | ✅ | Via move() | -| Card | color | String | ✅ | Via setColor() | -| Card | boardId/swimlaneId/listId | String | ✅ | Via move() | -| Card | archived | Boolean | ✅ | Via archive() | -| Card | description | Text | ✅ | Direct update | -| Card | customFields | Array | ✅ | Direct update | -| | | | | | -| Checklist | title | Text | ✅ | Via setTitle() | -| Checklist | sort | Number | ✅ | Direct update | -| Checklist | hideCheckedChecklistItems | Boolean | ✅ | Via toggle mutation | -| Checklist | hideAllChecklistItems | Boolean | ✅ | Via toggle mutation | -| | | | | | -| ChecklistItem | title | Text | ✅ | Via setTitle() | -| ChecklistItem | sort | Number | ✅ | Via move() | -| ChecklistItem | isFinished | Boolean | ✅ | Via check/uncheck/toggle | - -### Per-User Settings (NOT Persisted Across Boards) - -| Setting | Storage | Scope | Notes | -|---------|---------|-------|-------| -| List Widths | profile.listWidths | Per-board, per-user | ✅ Working | -| Swimlane Heights | profile.swimlaneHeights | Per-board, per-user | ✅ Working | -| Collapsed Swimlanes | profile.collapsedSwimlanes | Per-board, per-user | ✅ Working | -| Collapsed Lists | profile.collapsedLists | Per-board, per-user | ✅ Working | -| Card Collapsed State | profile.cardCollapsed | Global per-user | ⚠️ Name misleading | -| Card Maximized State | profile.cardMaximized | Global per-user | ✅ Working | -| Board Workspaces | profile.boardWorkspacesTree | Global per-user | ✅ Working | -| Board Workspace Assignments | profile.boardWorkspaceAssignments | Global per-user | ✅ Working | -| Board View Style | profile.boardView | Global per-user | ✅ Working | - ---- - -## Document History - -- **Created**: 2025-12-23 -- **Status**: Initial Audit Complete -- **Reviewed**: Swimlanes, Lists, Cards, Checklists, ChecklistItems, PositionHistory, Users -- **Next Review**: After addressing high-priority issues - diff --git a/docs/Security/PerUserDataAudit2025-12-23/Plan.txt b/docs/Security/PerUserDataAudit2025-12-23/Plan.txt deleted file mode 100644 index 899bd7aed..000000000 --- a/docs/Security/PerUserDataAudit2025-12-23/Plan.txt +++ /dev/null @@ -1,4 +0,0 @@ -Per-User vs Per-Board saving of data: Audit Plan 2025-12-23 - -All collapse state and swimlane height and list width is per-user and should always be persisted at users profile and localstorage, and validated that there is only numbers etc valid data. Invalid data and expired data is deleted from localstorage. swimlane height, list width and collapsed state need to be removed from board level, they are only user level. save swimlaneId always when saving data when swimlaneId is available. If there is no swimlaneId, show not-visible data at rescued data swimlane, similar like there is board settings / migrations to make invisible cards visible, by adding missing data. save position history to each user profile, and have undo redo buttons and list of history savepoints where is possible to return, similar like existing activities data schema, but no overwriting history. for board-level data, for each field (like description, comments etc) at Search All Boards have translateable options to also search from history of boards where user is member of board - diff --git a/docs/Security/PerUserDataAudit2025-12-23/QUICK_REFERENCE.md b/docs/Security/PerUserDataAudit2025-12-23/QUICK_REFERENCE.md deleted file mode 100644 index 799e788b5..000000000 --- a/docs/Security/PerUserDataAudit2025-12-23/QUICK_REFERENCE.md +++ /dev/null @@ -1,339 +0,0 @@ -# Wekan Persistence Improvements - Quick Reference - -## What Was Changed? - -### ❌ Removed -- Board-level `collapsed` field from Swimlanes -- Board-level `collapsed` field from Lists -- REST API endpoint for updating list `collapsed` status -- `collapse()` mutation from Swimlanes - -### ✅ Added -- Per-user position history with undo/redo -- LocalStorage validation and cleanup -- SwimlaneId validation migration -- Checkpoint/savepoint system for position history -- Enhanced data validation for all UI preferences - ---- - -## How It Works - -### Per-User Settings (Your Preferences) -These are NOW per-user and persisted: -- ✅ Swimlane collapse state -- ✅ List collapse state -- ✅ List width -- ✅ Swimlane height - -**Where it's stored:** -- Logged-in users: `user.profile` -- Non-logged-in users: Browser localStorage -- Validated & cleaned automatically - -### Position History (Card Movements) -Every time you move a card: -- Automatically tracked in `userPositionHistory` collection -- Stored with previous and new position -- Can be undone with `Meteor.call('userPositionHistory.undo', historyId)` -- Checkpoints can be created with `Meteor.call('userPositionHistory.createCheckpoint', boardId, name)` - -### Data Validation -All UI preference data is validated: -- List widths: 100-1000 pixels -- Swimlane heights: -1 (auto) or 50-2000 pixels -- Corrupted data: automatically removed -- Invalid data: rejected on write - ---- - -## For Users - -### What Changed? -- Your collapse preferences are now **private to you** (not shared with others) -- They persist across page reloads -- They work even if not logged in (saved in browser) -- Invalid data is automatically cleaned up - -### What You Can Do (Coming Soon) -- Undo/redo card movements -- Create savepoints of board state -- Restore to previous savepoints -- Use Ctrl+Z to undo - ---- - -## For Developers - -### New Collections - -**UserPositionHistory** -```javascript -{ - userId: String, - boardId: String, - entityType: 'card' | 'list' | 'swimlane' | 'checklist' | 'checklistItem', - entityId: String, - actionType: 'move' | 'create' | 'delete', - previousState: Object, - newState: Object, - isCheckpoint: Boolean, - checkpointName: String, - createdAt: Date -} -``` - -### New Meteor Methods - -```javascript -// Create a checkpoint -Meteor.call('userPositionHistory.createCheckpoint', boardId, 'name'); - -// Undo a change -Meteor.call('userPositionHistory.undo', historyId); - -// Get recent history -Meteor.call('userPositionHistory.getRecent', boardId, 50, (err, result) => { - // result is array of history entries -}); - -// Get checkpoints -Meteor.call('userPositionHistory.getCheckpoints', boardId, (err, checkpoints) => { - // result is array of checkpoints -}); - -// Restore to checkpoint -Meteor.call('userPositionHistory.restoreToCheckpoint', checkpointId); -``` - -### Updated Models - -**cards.js** -- `move()` now automatically tracks changes -- Uses `UserPositionHistory.trackChange()` - -**swimlanes.js** -- `collapsed` field removed (use profile.collapsedSwimlanes) -- `collapse()` mutation removed - -**lists.js** -- `collapsed` field removed (use profile.collapsedLists) -- Removed from REST API - -**users.js** -- Enhanced `getListWidthFromStorage()` with validation -- Enhanced `setSwimlaneHeightToStorage()` with validation -- Added automatic cleanup of invalid data - -### New Files - -``` -client/lib/localStorageValidator.js - - validateAndCleanLocalStorage() - - shouldRunCleanup() - - getValidatedLocalStorageData() - - setValidatedLocalStorageData() - - validators object with all validation functions - -models/lib/userStorageHelpers.js - - getValidatedNumber() - - setValidatedNumber() - - getValidatedBoolean() - - setValidatedBoolean() - -models/userPositionHistory.js - - UserPositionHistory collection - - Helpers: getDescription(), canUndo(), undo() - - Meteor methods for interaction - -server/migrations/ensureValidSwimlaneIds.js - - Runs automatically on startup - - Fixes cards/lists without swimlaneId - - Rescues orphaned data -``` - ---- - -## Migration Details - -### Automatic Migration: ensureValidSwimlaneIds - -Runs on server startup: - -1. **Finds cards without swimlaneId** - - Assigns them to default swimlane - -2. **Finds orphaned cards** - - SwimlaneId points to deleted swimlane - - Moves them to "Rescued Data" swimlane - -3. **Adds validation hooks** - - Prevents swimlaneId removal - - Auto-assigns on card creation - -**Tracking:** -```javascript -Migrations.findOne({ name: 'ensure-valid-swimlane-ids' }) -// Shows results of migration -``` - ---- - -## Data Examples - -### Before (Broken) -```javascript -// Swimlane with board-level collapse -{ - _id: 'swim123', - title: 'Development', - collapsed: true // ❌ Shared with all users! -} - -// Card without swimlaneId -{ - _id: 'card456', - title: 'Fix bug', - swimlaneId: undefined // ❌ No swimlane! -} -``` - -### After (Fixed) -```javascript -// Swimlane - no collapsed field -{ - _id: 'swim123', - title: 'Development', - // collapsed: removed ✅ -} - -// User's profile - has per-user settings -{ - _id: 'user789', - profile: { - collapsedSwimlanes: { - 'board123': { - 'swim123': true // ✅ Per-user! - } - }, - listWidths: { - 'board123': { - 'list456': 300 // ✅ Per-user! - } - } - } -} - -// Card with swimlaneId -{ - _id: 'card456', - title: 'Fix bug', - swimlaneId: 'swim123' // ✅ Always set! -} - -// Position history entry -{ - _id: 'hist789', - userId: 'user789', - boardId: 'board123', - entityType: 'card', - entityId: 'card456', - actionType: 'move', - previousState: { swimlaneId: 'swim123', listId: 'list456', sort: 1 }, - newState: { swimlaneId: 'swim123', listId: 'list789', sort: 2 }, - createdAt: ISODate('2025-12-23T07:00:00Z') -} -``` - ---- - -## Troubleshooting - -### Q: My collapse state isn't persisting -**A:** Make sure you're using the new per-user settings methods: -```javascript -user.setCollapsedSwimlane(boardId, swimlaneId, true); -user.getCollapsedSwimlaneFromStorage(boardId, swimlaneId); -``` - -### Q: I see "Rescued Data" swimlane with orphaned cards -**A:** Migration found cards pointing to deleted swimlanes. They're safe in the rescue swimlane. You can move them to proper swimlanes. - -### Q: localStorage is being cleared -**A:** That's intentional - we only keep valid data. Invalid/corrupted data is removed automatically during daily cleanup. - -### Q: How do I create a checkpoint? -**A:** Use the Meteor method: -```javascript -Meteor.call('userPositionHistory.createCheckpoint', boardId, 'Before big changes'); -``` - -### Q: How do I undo a card move? -**A:** Use the Meteor method: -```javascript -Meteor.call('userPositionHistory.undo', historyEntryId); -``` - ---- - -## Performance Notes - -### Storage -- localStorage: Max 50 boards, max 100 items per board -- UserPositionHistory: Max 1000 entries per user per board -- Auto-cleanup: Runs daily - -### Queries -- Limited to 100 results per query -- Indexed by userId, boardId, createdAt -- Fast checkpoint retrieval - -### Validation -- Runs on startup (once per day) -- Only validates if needed -- Removes excess data automatically - ---- - -## What's Next? - -### Coming Soon -- [ ] Undo/redo buttons in UI -- [ ] History sidebar -- [ ] Keyboard shortcuts (Ctrl+Z) -- [ ] Checkpoint UI - -### Future -- [ ] Field-level history (description, comments) -- [ ] Search across historical values -- [ ] Visual timeline -- [ ] Collaborative undo - ---- - -## Files to Know - -| File | Purpose | -|------|---------| -| [models/userPositionHistory.js](models/userPositionHistory.js) | Position history collection | -| [client/lib/localStorageValidator.js](client/lib/localStorageValidator.js) | Data validation | -| [server/migrations/ensureValidSwimlaneIds.js](server/migrations/ensureValidSwimlaneIds.js) | Automatic migration | -| [models/swimlanes.js](models/swimlanes.js) | Swimlane model | -| [models/lists.js](models/lists.js) | List model | -| [models/cards.js](models/cards.js) | Card model with tracking | - ---- - -## Questions? - -See detailed documentation: -- [ARCHITECTURE_IMPROVEMENTS.md](ARCHITECTURE_IMPROVEMENTS.md) - Complete guide -- [PERSISTENCE_AUDIT.md](PERSISTENCE_AUDIT.md) - System audit -- [IMPLEMENTATION_SUMMARY.md](IMPLEMENTATION_SUMMARY.md) - Implementation details -- [FIXES_CHECKLIST.md](FIXES_CHECKLIST.md) - What was fixed - ---- - -**Status**: ✅ Ready for use -**Last Updated**: 2025-12-23 - diff --git a/docs/Security/PerUserDataAudit2025-12-23/QUICK_START.md b/docs/Security/PerUserDataAudit2025-12-23/QUICK_START.md deleted file mode 100644 index 8e47ddc66..000000000 --- a/docs/Security/PerUserDataAudit2025-12-23/QUICK_START.md +++ /dev/null @@ -1,203 +0,0 @@ -# QUICK START - Data Persistence Architecture (2025-12-23) - -**STATUS**: ✅ Phase 1 Complete -**LOCATION**: `/home/wekan/repos/wekan/docs/Security/PerUserDataAudit2025-12-23/` - ---- - -## 🎯 The Change in 1 Sentence - -**Swimlane height and list width are now per-board (shared), not per-user (private).** - ---- - -## 📝 What Changed - -### Swimlanes (swimlanes.js) -```javascript -✅ ADDED: height: { type: Number, default: -1, range: -1 or 50-2000 } -📍 Line: ~108-130 -``` - -### Lists (lists.js) -```javascript -✅ ADDED: width: { type: Number, default: 272, range: 100-1000 } -📍 Line: ~162-182 -``` - -### Cards, Checklists, ChecklistItems -```javascript -✅ NO CHANGE - Positions already per-board in sort field -``` - ---- - -## 📊 Per-Board vs Per-User Quick Reference - -### ✅ PER-BOARD (All Users See Same) -- Swimlane height -- List width -- Card/checklist/checklistItem positions -- All titles, colors, descriptions - -### 🔒 PER-USER (Only You See Yours) -- Collapsed swimlanes (yes/no) -- Collapsed lists (yes/no) -- Hidden label text (yes/no) - ---- - -## 📁 Documentation Quick Links - -| Need | File | Time | -|------|------|------| -| Quick overview | [README.md](README.md) | 5 min | -| For management | [EXECUTIVE_SUMMARY.md](EXECUTIVE_SUMMARY.md) | 5 min | -| Current status | [CURRENT_STATUS.md](CURRENT_STATUS.md) | 5 min | -| Full architecture | [DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md) | 15 min | -| How to implement | [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) | 20 min | -| Verify changes | [SCHEMA_CHANGES_VERIFICATION.md](SCHEMA_CHANGES_VERIFICATION.md) | 10 min | -| Quick lookup | [QUICK_REFERENCE.md](QUICK_REFERENCE.md) | 3 min | -| What's done | [COMPLETION_SUMMARY.md](COMPLETION_SUMMARY.md) | 10 min | - ---- - -## ✅ What's Complete (Phase 1) - -- [x] Schema: Added height to swimlanes -- [x] Schema: Added width to lists -- [x] Validation: Both fields validate ranges -- [x] Documentation: 12 comprehensive guides -- [x] Backward compatible: Both fields optional - ---- - -## ⏳ What's Left (Phases 2-4) - -- [ ] Phase 2: Refactor user model (~2-4h) -- [ ] Phase 3: Migrate data (~1-2h) -- [ ] Phase 4: Update UI (~4-6h) - -See [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) for details - ---- - -## 🔍 Quick Facts - -| Item | Value | -|------|-------| -| Files Modified | 2 (swimlanes.js, lists.js) | -| Fields Added | 2 (height, width) | -| Documentation Files | 12 (4,400+ lines) | -| Validation Rules | 2 (range checks) | -| Backward Compatible | ✅ Yes | -| Data Loss Risk | ✅ None | -| Time to Read Docs | ~1 hour | -| Time to Implement Phase 2 | ~2-4 hours | - ---- - -## 🚀 Success Criteria - -✅ Per-board height/width storage -✅ Per-user collapse/visibility only -✅ Validation enforced -✅ Backward compatible -✅ Documentation complete -✅ Implementation guidance provided - ---- - -## 🎓 For Team Members - -**New to this?** -1. Read: [README.md](README.md) (5 min) -2. Skim: [CURRENT_STATUS.md](CURRENT_STATUS.md) (5 min) -3. Reference: [DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md) as needed - -**Implementing Phase 2?** -1. Read: [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) Section 2 -2. Code: Follow exact steps -3. Test: Use provided checklist - -**Reviewing changes?** -1. Check: [SCHEMA_CHANGES_VERIFICATION.md](SCHEMA_CHANGES_VERIFICATION.md) -2. Review: swimlanes.js and lists.js -3. Verify: Validation logic - ---- - -## 💾 Files Modified - -``` -/home/wekan/repos/wekan/ -├── models/ -│ ├── swimlanes.js ✅ height field added -│ ├── lists.js ✅ width field added -│ ├── cards.js ✅ no change (already correct) -│ ├── checklists.js ✅ no change (already correct) -│ └── checklistItems.js ✅ no change (already correct) -└── docs/Security/PerUserDataAudit2025-12-23/ - ├── README.md - ├── EXECUTIVE_SUMMARY.md - ├── COMPLETION_SUMMARY.md - ├── CURRENT_STATUS.md - ├── DATA_PERSISTENCE_ARCHITECTURE.md - ├── IMPLEMENTATION_GUIDE.md - ├── SCHEMA_CHANGES_VERIFICATION.md - ├── QUICK_REFERENCE.md (original) - └── [7 other docs from earlier phases] -``` - ---- - -## 🧪 Quick Test - -```javascript -// Test swimlane height validation -Swimlanes.insert({ boardId: 'b1', height: -1 }) // ✅ OK (auto) -Swimlanes.insert({ boardId: 'b1', height: 100 }) // ✅ OK (valid) -Swimlanes.insert({ boardId: 'b1', height: 25 }) // ❌ FAILS (too small) -Swimlanes.insert({ boardId: 'b1', height: 3000 }) // ❌ FAILS (too large) - -// Test list width validation -Lists.insert({ boardId: 'b1', width: 272 }) // ✅ OK (default) -Lists.insert({ boardId: 'b1', width: 500 }) // ✅ OK (valid) -Lists.insert({ boardId: 'b1', width: 50 }) // ❌ FAILS (too small) -Lists.insert({ boardId: 'b1', width: 2000 }) // ❌ FAILS (too large) -``` - ---- - -## 📞 Questions? - -| Question | Answer Location | -|----------|-----------------| -| What changed? | [COMPLETION_SUMMARY.md](COMPLETION_SUMMARY.md) | -| Why did it change? | [EXECUTIVE_SUMMARY.md](EXECUTIVE_SUMMARY.md) | -| What's per-board? | [DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md) | -| What's per-user? | [DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md) | -| How do I implement Phase 2? | [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) | -| Is it backward compatible? | [SCHEMA_CHANGES_VERIFICATION.md](SCHEMA_CHANGES_VERIFICATION.md) | - ---- - -## 🎯 Next Steps - -1. **Read the docs** (1 hour) - - Start with [README.md](README.md) - - Skim [CURRENT_STATUS.md](CURRENT_STATUS.md) - -2. **Review code changes** (15 min) - - Check swimlanes.js (line ~108-130) - - Check lists.js (line ~162-182) - -3. **Plan Phase 2** (1 hour) - - Read [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) Section 2 - - Estimate effort needed - - Schedule implementation - ---- - -**Status**: ✅ READY FOR PHASE 2 - diff --git a/docs/Security/PerUserDataAudit2025-12-23/README.md b/docs/Security/PerUserDataAudit2025-12-23/README.md deleted file mode 100644 index 43047f106..000000000 --- a/docs/Security/PerUserDataAudit2025-12-23/README.md +++ /dev/null @@ -1,334 +0,0 @@ -# Per-User Data Audit 2025-12-23 - Complete Documentation Index - -**Last Updated**: 2025-12-23 -**Status**: ✅ Current (All data persistence architecture up-to-date) -**Scope**: Swimlanes, Lists, Cards, Checklists, ChecklistItems - positions, widths, heights, colors, titles - ---- - -## 📋 Documentation Overview - -This folder contains the complete, current documentation for Wekan's data persistence architecture as of December 23, 2025. - -**Key Change**: Swimlane height and list width are now **per-board** (stored in documents, shared with all users), not per-user. - ---- - -## 📚 Documents (Read In This Order) - -### 1. **[CURRENT_STATUS.md](CURRENT_STATUS.md)** 🟢 START HERE -**Purpose**: Quick status overview of what's been done and what's pending -**Read Time**: 5 minutes -**Contains**: -- Key decision on data classification -- What's completed vs pending -- Before/after examples -- Testing requirements -- Integration phases - -**Best For**: Getting current status quickly - ---- - -### 2. **[DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md)** 📖 REFERENCE -**Purpose**: Complete architecture specification -**Read Time**: 15 minutes -**Contains**: -- Full data classification matrix (per-board vs per-user) -- Where each field is stored -- MongoDB schema definitions -- Cookie/localStorage for public users -- Data flow diagrams -- Validation rules -- Security implications -- Testing checklist - -**Best For**: Understanding the complete system - ---- - -### 3. **[IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md)** 🛠️ DOING THE WORK -**Purpose**: Step-by-step implementation instructions -**Read Time**: 20 minutes -**Contains**: -- Changes already completed ✅ -- Changes still needed ⏳ -- Code examples for refactoring -- Migration script template -- Testing checklist -- Rollback plan -- Files modified reference - -**Best For**: Implementing the remaining phases - ---- - -### 4. **[SCHEMA_CHANGES_VERIFICATION.md](SCHEMA_CHANGES_VERIFICATION.md)** ✅ VERIFICATION -**Purpose**: Verification that schema changes are correct -**Read Time**: 10 minutes -**Contains**: -- Exact fields added (with line numbers) -- Validation rule verification -- Data type classification -- Migration path status -- Code review checklist -- Integration notes - -**Best For**: Verifying all changes are correct - ---- - -### 5. **[QUICK_REFERENCE.md](QUICK_REFERENCE.md)** ⚡ QUICK LOOKUP -**Purpose**: Quick reference for key information -**Read Time**: 3 minutes -**Contains**: -- What changed (removed/added/kept) -- How it works (per-user vs per-board) -- Troubleshooting -- Performance notes -- Which files to know about - -**Best For**: Quick lookups and troubleshooting - ---- - -## 🎯 At a Glance - -### The Core Change - -**BEFORE** (Mixed/Wrong): -- Swimlane height: Stored per-user in user.profile -- List width: Stored per-user in user.profile -- Cards could look different dimensions for different users - -**NOW** (Correct): -- Swimlane height: Stored per-board in swimlane document -- List width: Stored per-board in list document -- All users see same dimensions (shared layout) -- Only collapse state is per-user (private preference) - ---- - -### What's Per-Board ✅ (ALL Users See Same) - -``` -Swimlane: - - title, color, height, sort, archived - -List: - - title, color, width, sort, archived, wipLimit, starred - -Card: - - title, color, description, swimlaneId, listId, sort, archived - -Checklist: - - title, sort, hideCheckedItems, hideAllItems - -ChecklistItem: - - title, sort, isFinished -``` - -### What's Per-User 🔒 (Only YOU See Yours) - -``` -User Preferences: - - collapsedSwimlanes[boardId][swimlaneId] (true/false) - - collapsedLists[boardId][listId] (true/false) - - hideMiniCardLabelText[boardId] (true/false) -``` - ---- - -## ✅ Completed (Phase 1) - -- [x] **Schema Addition** - - Added `swimlanes.height` field (default: -1, range: -1 or 50-2000) - - Added `lists.width` field (default: 272, range: 100-1000) - - Both with validation and backward compatibility - -- [x] **Documentation** - - Complete architecture specification - - Implementation guide with code examples - - Migration script template - - Verification checklist - -- [x] **Verification** - - Schema changes verified correct - - Validation logic reviewed - - Code samples provided - - Testing plans documented - ---- - -## ⏳ Pending (Phase 2-4) - -- [ ] **User Model Refactoring** (Phase 2) - - Refactor user methods to read heights/widths from documents - - Remove per-user storage from user.profile - - Update user schema definition - -- [ ] **Data Migration** (Phase 3) - - Create migration script (template in IMPLEMENTATION_GUIDE.md) - - Migrate existing per-user data to per-board - - Track migration status - - Verify no data loss - -- [ ] **UI Integration** (Phase 4) - - Update client code - - Update Meteor methods - - Update subscriptions - - Test with multiple users - ---- - -## 📊 Data Classification Summary - -### Per-Board (Shared with All Users) -| Data | Current | New | -|------|---------|-----| -| Swimlane height | ❌ Per-user (wrong) | ✅ Per-board (correct) | -| List width | ❌ Per-user (wrong) | ✅ Per-board (correct) | -| Card position | ✅ Per-board | ✅ Per-board | -| Checklist position | ✅ Per-board | ✅ Per-board | -| ChecklistItem position | ✅ Per-board | ✅ Per-board | - -### Per-User (Private to You) -| Data | Current | New | -|------|---------|-----| -| Collapse swimlane | ✅ Per-user | ✅ Per-user | -| Collapse list | ✅ Per-user | ✅ Per-user | -| Hide label text | ✅ Per-user | ✅ Per-user | - ---- - -## 🔍 Quick Facts - -- **Total Files Modified So Far**: 2 (swimlanes.js, lists.js) -- **Total Files Documented**: 5 markdown files -- **Schema Fields Added**: 2 (height, width) -- **Validation Rules Added**: 2 (heightOutOfRange, widthOutOfRange) -- **Per-Board Data Types**: 5 entity types × multiple fields -- **Per-User Data Types**: 3 preference types -- **Backward Compatibility**: ✅ Yes (both fields optional) -- **Data Loss Risk**: ✅ None (old data preserved until migration) - ---- - -## 🚀 How to Use This Documentation - -### For Developers Joining Now - -1. Read **[CURRENT_STATUS.md](CURRENT_STATUS.md)** - 5 min overview -2. Skim **[DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md)** - understand the system -3. Reference **[IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md)** - when doing Phase 2 - -### For Reviewing Changes - -1. Read **[SCHEMA_CHANGES_VERIFICATION.md](SCHEMA_CHANGES_VERIFICATION.md)** - verify what was done -2. Check actual files: swimlanes.js, lists.js -3. Approve or request changes - -### For Implementing Remaining Work - -1. **Phase 2 (User Refactoring)**: See [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) Section 2 -2. **Phase 3 (Migration)**: Use template in [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) Section 4 -3. **Phase 4 (UI)**: See [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) Section 3 - -### For Troubleshooting - -- Quick answers: **[QUICK_REFERENCE.md](QUICK_REFERENCE.md)** -- Detailed reference: **[DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md)** - ---- - -## 📞 Questions Answered - -### "What data is per-board?" -See **[DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md)** Section: Data Classification Matrix - -### "What data is per-user?" -See **[DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md)** Section: Data Classification Matrix - -### "Where is swimlane height stored?" -- **New**: In swimlane document (per-board) -- **Old**: In user.profile (per-user) - being replaced -- See **[SCHEMA_CHANGES_VERIFICATION.md](SCHEMA_CHANGES_VERIFICATION.md)** for verification - -### "Where is list width stored?" -- **New**: In list document (per-board) -- **Old**: In user.profile (per-user) - being replaced -- See **[SCHEMA_CHANGES_VERIFICATION.md](SCHEMA_CHANGES_VERIFICATION.md)** for verification - -### "How do I migrate old data?" -See **[IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md)** Section 4 for migration script template - -### "What should I do next?" -See **[CURRENT_STATUS.md](CURRENT_STATUS.md)** Section: Integration Path → Phase 2 - -### "Is there a migration risk?" -No - see **[IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md)** Section 7: Rollback Plan - -### "Are there validation rules?" -Yes - see **[DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md)** Section: Validation Rules - ---- - -## 🔄 Document Update Schedule - -| Document | Last Updated | Next Review | -|----------|--------------|-------------| -| [CURRENT_STATUS.md](CURRENT_STATUS.md) | 2025-12-23 | After Phase 2 | -| [DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md) | 2025-12-23 | If architecture changes | -| [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) | 2025-12-23 | After Phase 2 complete | -| [SCHEMA_CHANGES_VERIFICATION.md](SCHEMA_CHANGES_VERIFICATION.md) | 2025-12-23 | After Phase 2 complete | -| [QUICK_REFERENCE.md](QUICK_REFERENCE.md) | 2025-12-23 | After Phase 3 complete | - ---- - -## ✨ Key Achievements - -✅ **Clear Architecture**: Swimlane height and list width are now definitively per-board -✅ **Schema Validation**: Both fields have custom validation functions -✅ **Documentation**: 5 comprehensive documents covering all aspects -✅ **Backward Compatible**: Old data preserved, transition safe -✅ **Implementation Ready**: Code examples and migration scripts provided -✅ **Future-Proof**: Clear path for remaining phases - ---- - -## 📝 Notes - -- All data classification decisions made with input from security audit -- Per-board height/width means better collaboration (shared layout) -- Per-user collapse/visibility means better individual workflow -- Migration can happen at any time with no user downtime -- Testing templates provided for all phases - ---- - -## 📍 File Location Reference - -All files are in: `/home/wekan/repos/wekan/docs/Security/PerUserDataAudit2025-12-23/` - -``` -PerUserDataAudit2025-12-23/ -├── CURRENT_STATUS.md ← Start here -├── DATA_PERSISTENCE_ARCHITECTURE.md ← Complete spec -├── IMPLEMENTATION_GUIDE.md ← How to implement -├── SCHEMA_CHANGES_VERIFICATION.md ← Verification -├── QUICK_REFERENCE.md ← Quick lookup -├── README.md ← This file -├── QUICK_REFERENCE.md ← Previous doc -├── ARCHITECTURE_IMPROVEMENTS.md ← From Phase 1 -├── PERSISTENCE_AUDIT.md ← Initial audit -├── IMPLEMENTATION_SUMMARY.md ← Phase 1 summary -├── FIXES_CHECKLIST.md ← Bug fixes -└── Plan.txt ← Original plan -``` - ---- - -**Status**: ✅ COMPLETE AND CURRENT -**Last Review**: 2025-12-23 -**Next Phase**: User Model Refactoring (Phase 2) - diff --git a/docs/Security/PerUserDataAudit2025-12-23/SCHEMA_CHANGES_VERIFICATION.md b/docs/Security/PerUserDataAudit2025-12-23/SCHEMA_CHANGES_VERIFICATION.md deleted file mode 100644 index f61e970a8..000000000 --- a/docs/Security/PerUserDataAudit2025-12-23/SCHEMA_CHANGES_VERIFICATION.md +++ /dev/null @@ -1,294 +0,0 @@ -# Schema Changes Verification Checklist - -**Date**: 2025-12-23 -**Status**: ✅ Verification Complete - ---- - -## Schema Addition Checklist - -### Swimlanes.js - Height Field ✅ - -**File**: [models/swimlanes.js](../../../models/swimlanes.js) - -**Location**: Lines ~108-130 (after type field, before closing brace) - -**Added Field**: -```javascript -height: { - /** - * The height of the swimlane in pixels. - * -1 = auto-height (default) - * 50-2000 = fixed height in pixels - */ - type: Number, - optional: true, - defaultValue: -1, - custom() { - const h = this.value; - if (h !== -1 && (h < 50 || h > 2000)) { - return 'heightOutOfRange'; - } - }, -}, -``` - -**Validation Rules**: -- ✅ Type: Number -- ✅ Default: -1 (auto-height) -- ✅ Optional: true (backward compatible) -- ✅ Custom validation: -1 OR 50-2000 -- ✅ Out of range returns 'heightOutOfRange' error - -**Status**: ✅ VERIFIED - Field added with correct validation - ---- - -### Lists.js - Width Field ✅ - -**File**: [models/lists.js](../../../models/lists.js) - -**Location**: Lines ~162-182 (after type field, before closing brace) - -**Added Field**: -```javascript -width: { - /** - * The width of the list in pixels (100-1000). - * Default width is 272 pixels. - */ - type: Number, - optional: true, - defaultValue: 272, - custom() { - const w = this.value; - if (w < 100 || w > 1000) { - return 'widthOutOfRange'; - } - }, -}, -``` - -**Validation Rules**: -- ✅ Type: Number -- ✅ Default: 272 pixels -- ✅ Optional: true (backward compatible) -- ✅ Custom validation: 100-1000 only -- ✅ Out of range returns 'widthOutOfRange' error - -**Status**: ✅ VERIFIED - Field added with correct validation - ---- - -## Data Type Classification - -### Per-Board Storage (MongoDB Documents) ✅ - -| Entity | Field | Storage | Type | Default | Range | -|--------|-------|---------|------|---------|-------| -| Swimlane | height | swimlanes.height | Number | -1 | -1 or 50-2000 | -| List | width | lists.width | Number | 272 | 100-1000 | -| Card | sort | cards.sort | Number | varies | unlimited | -| Card | swimlaneId | cards.swimlaneId | String | required | any valid ID | -| Card | listId | cards.listId | String | required | any valid ID | -| Checklist | sort | checklists.sort | Number | varies | unlimited | -| ChecklistItem | sort | checklistItems.sort | Number | varies | unlimited | - -**Shared**: ✅ All users see the same value -**Persisted**: ✅ Survives across sessions -**Conflict**: ✅ No per-user override - ---- - -### Per-User Storage (User Profile) ✅ - -| Entity | Field | Storage | Scope | -|--------|-------|---------|-------| -| User | Collapse Swimlane | profile.collapsedSwimlanes[boardId][swimlaneId] | Per-user | -| User | Collapse List | profile.collapsedLists[boardId][listId] | Per-user | -| User | Hide Labels | profile.hideMiniCardLabelText[boardId] | Per-user | - -**Private**: ✅ Each user has own value -**Persisted**: ✅ Survives across sessions -**Isolated**: ✅ No visibility to other users - ---- - -## Migration Path - -### Phase 1: Schema Addition ✅ COMPLETE - -- ✅ Swimlanes.height field added -- ✅ Lists.width field added -- ✅ Both with validation -- ✅ Both optional for backward compatibility -- ✅ Default values set - -### Phase 2: User Model Updates ⏳ TODO - -- ⏳ Refactor user.getListWidth() → read from list.width -- ⏳ Refactor user.getSwimlaneHeight() → read from swimlane.height -- ⏳ Remove per-user width storage from user.profile -- ⏳ Remove per-user height storage from user.profile - -### Phase 3: Data Migration ⏳ TODO - -- ⏳ Create migration script (template in IMPLEMENTATION_GUIDE.md) -- ⏳ Migrate user.profile.listWidths → list.width -- ⏳ Migrate user.profile.swimlaneHeights → swimlane.height -- ⏳ Mark old fields for removal - -### Phase 4: UI Integration ⏳ TODO - -- ⏳ Update client code to use new locations -- ⏳ Update Meteor methods to update documents -- ⏳ Remove old user profile access patterns - ---- - -## Backward Compatibility - -### Existing Data Handled Correctly - -**Scenario**: Database has old data with per-user widths/heights - -✅ **Solution**: -- New fields in swimlane/list documents have defaults -- Old user.profile data remains until migration -- Code can read from either location during transition -- Migration script safely moves data - -### Migration Safety - -✅ **Validation**: All values validated before write -✅ **Type Safety**: SimpleSchema enforces numeric types -✅ **Range Safety**: Custom validators reject out-of-range values -✅ **Rollback**: Data snapshot before migration (mongodump) -✅ **Tracking**: Migration status recorded in Migrations collection - ---- - -## Testing Verification - -### Schema Tests - -```javascript -// Swimlane height validation tests -✅ Swimlanes.insert({ swimlaneId: 's1', height: -1 }) // Auto-height OK -✅ Swimlanes.insert({ swimlaneId: 's2', height: 50 }) // Minimum OK -✅ Swimlanes.insert({ swimlaneId: 's3', height: 2000 }) // Maximum OK -❌ Swimlanes.insert({ swimlaneId: 's4', height: 25 }) // Too small - REJECTED -❌ Swimlanes.insert({ swimlaneId: 's5', height: 3000 }) // Too large - REJECTED - -// List width validation tests -✅ Lists.insert({ listId: 'l1', width: 100 }) // Minimum OK -✅ Lists.insert({ listId: 'l2', width: 500 }) // Medium OK -✅ Lists.insert({ listId: 'l3', width: 1000 }) // Maximum OK -❌ Lists.insert({ listId: 'l4', width: 50 }) // Too small - REJECTED -❌ Lists.insert({ listId: 'l5', width: 2000 }) // Too large - REJECTED -``` - ---- - -## Documentation Verification - -### Created Documents - -| Document | Purpose | Status | -|----------|---------|--------| -| [DATA_PERSISTENCE_ARCHITECTURE.md](DATA_PERSISTENCE_ARCHITECTURE.md) | Full architecture specification | ✅ Created | -| [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) | Implementation steps and migration template | ✅ Created | -| [CURRENT_STATUS.md](CURRENT_STATUS.md) | Status summary and next steps | ✅ Created | -| [SCHEMA_CHANGES_VERIFICATION.md](SCHEMA_CHANGES_VERIFICATION.md) | This file - verification checklist | ✅ Created | - ---- - -## Code Review Checklist - -### Swimlanes.js ✅ - -- ✅ Height field added to schema -- ✅ Comment explains per-board storage -- ✅ Validation function checks range -- ✅ Optional: true for backward compatibility -- ✅ defaultValue: -1 (auto-height) -- ✅ Field added before closing brace -- ✅ No syntax errors -- ✅ No breaking changes to existing fields - -### Lists.js ✅ - -- ✅ Width field added to schema -- ✅ Comment explains per-board storage -- ✅ Validation function checks range -- ✅ Optional: true for backward compatibility -- ✅ defaultValue: 272 (standard width) -- ✅ Field added before closing brace -- ✅ No syntax errors -- ✅ No breaking changes to existing fields - ---- - -## Integration Notes - -### Before Next Phase - -1. **Verify Schema Validation** - ```bash - cd /home/wekan/repos/wekan - meteor shell - > Swimlanes.insert({ boardId: 'test', height: -1 }) // Should work - > Swimlanes.insert({ boardId: 'test', height: 25 }) // Should fail - ``` - -2. **Check Database** - ```bash - mongo wekan - > db.swimlanes.findOne() // Check height field exists - > db.lists.findOne() // Check width field exists - ``` - -3. **Verify No Errors** - - Check console for schema validation errors - - Run existing tests to ensure backward compatibility - - Verify app starts without errors - -### Next Phase (User Model) - -See [IMPLEMENTATION_GUIDE.md](IMPLEMENTATION_GUIDE.md) for detailed steps: -1. Refactor user methods -2. Remove per-user storage from schema -3. Create migration script -4. Test data movement - ---- - -## Sign-Off - -### Schema Changes Completed ✅ - -**Swimlanes.js**: -- ✅ Height field added with validation -- ✅ Backward compatible -- ✅ Documentation updated - -**Lists.js**: -- ✅ Width field added with validation -- ✅ Backward compatible -- ✅ Documentation updated - -### Ready for Review ✅ - -All schema changes are: -- ✅ Syntactically correct -- ✅ Logically sound -- ✅ Backward compatible -- ✅ Well documented -- ✅ Ready for deployment - ---- - -**Last Verified**: 2025-12-23 -**Verified By**: Code review -**Status**: ✅ COMPLETE - diff --git a/docs/Security/Sandboxes/vscode/README.md b/docs/Security/Sandboxes/vscode/README.md deleted file mode 100644 index cb7267542..000000000 --- a/docs/Security/Sandboxes/vscode/README.md +++ /dev/null @@ -1,82 +0,0 @@ -# Secure Sandbox: VSCode at Debian 13 amd64 - -Related files at this repo `.vscode` at [this commit](https://github.com/wekan/wekan/commit/639ac9549f88069d8569de777c533ab4c9438088). - -## 1) Install Debian - -Install Debian with username `wekan`, so that WeKan repo here, only directory where VSCode will have access: -``` -/home/wekan/repos/wekan -``` - -## 2) Install Flatpak and VSCode - -``` -sudo apt install flatpak - -sudo apt install gnome-software-plugin-flatpak - -flatpak remote-add --if-not-exists flathub https://dl.flathub.org/repo/flathub.flatpakrepo - -sudo reboot - -flatpak install flathub com.visualstudio.code -``` - -## 3) Edit VSCode desktop icon - -``` -nano ~/.local/share/applications/wekan-vscode.desktop -``` -Content: -``` -[Desktop Entry] -Name=VS Code - WeKan -Comment=Open the WeKan project with Flatpak -Exec=flatpak run com.visualstudio.code /home/wekan/repos/wekan -Icon=com.visualstudio.code -Terminal=false -Type=Application -Categories=Development;IDE; -StartupWMClass=code -``` - -## 4) Force VS Code to use the internal (isolated) browser - -This setting is also added as git commit to VSCode settings. - -This is the most important step. If this is "native", it will use the operating system window that sees everything. - -1. Open VS Code. -2. Press `Ctrl + ,` (options). -3. Type in search: **Dialogs: Custom** -4. Change the `Files: Simple Dialog` setting to **on** (check the box). -5. Restart VS Code. - -## 5) Set the strictest sandbox possible (in Terminal) - -Run these two commands (the first clears everything, the second sets limits): - -```bash -# Reset previous attempts -sudo flatpak override --reset com.visualstudio.code - -# Block EVERYTHING except the display and the wekan folder -sudo flatpak override com.visualstudio.code \ ---nofilesystem=home \ ---nofilesystem=host \ ---nofilesystem=xdg-run/gvfs \ ---nofilesystem=xdg-run/gvfsd \ ---filesystem=~/repos/wekan:rw \ ---device=all \ ---socket=wayland \ ---socket=x11 - -``` - -## 6) Test "File -> Open Folder" - -Now when you go to **File -> Open Folder**: - -1. You will no longer see the fancy system file window, but VS Code's own, simple list. -2. If you try to go to the parent folder or somewhere else, **the list is empty** or it only shows `~/repos/wekan`. diff --git a/find.sh b/find.sh index 643185fd2..7c11e282d 100755 --- a/find.sh +++ b/find.sh @@ -15,4 +15,4 @@ fi #find . | grep -v node_modules | grep -v .build | grep -v .meteor | grep -v .git | xargs grep --no-messages $1 | less #find . -print0 | grep -v node_modules | grep -v .build | grep -v .meteor | grep -v .git | xargs -0 grep --no-messages $1 | less -find . -type f -not -path '*/node_modules/*' -not -path '*/.build/*' -not -path '*/.meteor/*' -not -path '*/.git/*' -not -path '*/imports/i18n/data/*' -exec grep -I --no-messages "$1" {} + | less +find . | grep -v node_modules | grep -v .build | grep -v .meteor | grep -v .git | xargs grep -I --no-messages $1 | less diff --git a/imports/attachmentMigrationClient.js b/imports/attachmentMigrationClient.js deleted file mode 100644 index 2ae57d746..000000000 --- a/imports/attachmentMigrationClient.js +++ /dev/null @@ -1,4 +0,0 @@ -import { Mongo } from 'meteor/mongo'; - -// Client-side collection mirror for attachment migration status -export const AttachmentMigrationStatus = new Mongo.Collection('attachmentMigrationStatus'); diff --git a/imports/cronMigrationClient.js b/imports/cronMigrationClient.js deleted file mode 100644 index e9817b493..000000000 --- a/imports/cronMigrationClient.js +++ /dev/null @@ -1,144 +0,0 @@ -import { Meteor } from 'meteor/meteor'; -import { ReactiveVar } from 'meteor/reactive-var'; -import { Mongo } from 'meteor/mongo'; -import { Tracker } from 'meteor/tracker'; - -// Client-side collection mirror -export const CronJobStatus = new Mongo.Collection('cronJobStatus'); - -export const cronMigrationProgress = new ReactiveVar(0); -export const cronMigrationStatus = new ReactiveVar(''); -export const cronMigrationCurrentStep = new ReactiveVar(''); -export const cronMigrationSteps = new ReactiveVar([]); -export const cronIsMigrating = new ReactiveVar(false); -export const cronJobs = new ReactiveVar([]); -export const cronMigrationCurrentStepNum = new ReactiveVar(0); -export const cronMigrationTotalSteps = new ReactiveVar(0); -export const cronMigrationCurrentAction = new ReactiveVar(''); -export const cronMigrationJobProgress = new ReactiveVar(0); -export const cronMigrationJobStepNum = new ReactiveVar(0); -export const cronMigrationJobTotalSteps = new ReactiveVar(0); -export const cronMigrationEtaSeconds = new ReactiveVar(null); -export const cronMigrationElapsedSeconds = new ReactiveVar(null); -export const cronMigrationCurrentNumber = new ReactiveVar(null); -export const cronMigrationCurrentName = new ReactiveVar(''); - -function fetchProgress() { - Meteor.call('cron.getMigrationProgress', (err, res) => { - if (err) return; - if (!res) return; - cronMigrationProgress.set(res.progress || 0); - cronMigrationStatus.set(res.status || ''); - cronMigrationCurrentStep.set(res.currentStep || ''); - cronMigrationSteps.set(res.steps || []); - cronIsMigrating.set(res.isMigrating || false); - cronMigrationCurrentStepNum.set(res.currentStepNum || 0); - cronMigrationTotalSteps.set(res.totalSteps || 0); - cronMigrationCurrentAction.set(res.currentAction || ''); - cronMigrationJobProgress.set(res.jobProgress || 0); - cronMigrationJobStepNum.set(res.jobStepNum || 0); - cronMigrationJobTotalSteps.set(res.jobTotalSteps || 0); - cronMigrationEtaSeconds.set(res.etaSeconds ?? null); - cronMigrationElapsedSeconds.set(res.elapsedSeconds ?? null); - cronMigrationCurrentNumber.set(res.migrationNumber ?? null); - cronMigrationCurrentName.set(res.migrationName || ''); - - if ((!res.steps || res.steps.length === 0) && !res.isMigrating) { - const loaded = res.migrationStepsLoaded || 0; - const total = res.migrationStepsTotal || 0; - if (total > 0) { - cronMigrationStatus.set( - `Updating Select Migration dropdown menu (${loaded}/${total})` - ); - } else { - cronMigrationStatus.set('Updating Select Migration dropdown menu'); - } - } - }); -} - -if (Meteor.isClient) { - // Subscribe to migration status updates (real-time pub/sub) - Meteor.subscribe('cronMigrationStatus'); - - // Subscribe to cron jobs list (replaces polling cron.getJobs) - Meteor.subscribe('cronJobs'); - - // Subscribe to detailed migration progress data - Meteor.subscribe('migrationProgress'); - - // Reactively update cron jobs from published collection - Tracker.autorun(() => { - const jobDocs = CronJobStatus.find({}).fetch(); - cronJobs.set(jobDocs); - }); - - // Reactively update status from published data - Tracker.autorun(() => { - const statusDoc = CronJobStatus.findOne({ jobId: 'migration' }); - if (statusDoc) { - cronIsMigrating.set(statusDoc.status === 'running' || statusDoc.status === 'starting'); - - // Update status text based on job status - if (statusDoc.status === 'starting') { - cronMigrationStatus.set(statusDoc.statusMessage || 'Starting migrations...'); - } else if (statusDoc.status === 'pausing') { - cronMigrationStatus.set(statusDoc.statusMessage || 'Pausing migrations...'); - } else if (statusDoc.status === 'stopping') { - cronMigrationStatus.set(statusDoc.statusMessage || 'Stopping migrations...'); - } else if (statusDoc.statusMessage) { - cronMigrationStatus.set(statusDoc.statusMessage); - } - - if (statusDoc.progress !== undefined) { - cronMigrationJobProgress.set(statusDoc.progress); - } - } - }); - - // Reactively update job progress from migration details - Tracker.autorun(() => { - const runningJob = CronJobStatus.findOne( - { status: 'running', jobType: 'migration' }, - { sort: { updatedAt: -1 } } - ); - - if (runningJob) { - cronMigrationJobProgress.set(runningJob.progress || 0); - - // Get ETA information if available - if (runningJob.startedAt && runningJob.progress > 0) { - const elapsed = Math.round((Date.now() - runningJob.startedAt.getTime()) / 1000); - const eta = Math.round((elapsed * (100 - runningJob.progress)) / runningJob.progress); - cronMigrationEtaSeconds.set(eta); - cronMigrationElapsedSeconds.set(elapsed); - } - } - }); - - // Initial fetch for migration steps and other data - fetchProgress(); - - // Poll periodically only for migration steps dropdown (non-reactive data) - // Increased from 5000ms to 10000ms since most data is now reactive via pub/sub - Meteor.setInterval(() => { - fetchProgress(); - }, 10000); -} - -export default { - cronMigrationProgress, - cronMigrationStatus, - cronMigrationCurrentStep, - cronMigrationSteps, - cronIsMigrating, - cronJobs, - cronMigrationCurrentAction, - cronMigrationJobProgress, - cronMigrationJobStepNum, - cronMigrationJobTotalSteps, - cronMigrationEtaSeconds, - cronMigrationElapsedSeconds, - cronMigrationCurrentNumber, - cronMigrationCurrentName, -}; diff --git a/imports/i18n/accounts.js b/imports/i18n/accounts.js index 27e28c811..e17540f15 100644 --- a/imports/i18n/accounts.js +++ b/imports/i18n/accounts.js @@ -5,10 +5,6 @@ import { TAPi18n } from './tap'; T9n.setTracker({ Tracker }); -const loginForbiddenTranslation = { - 'error.accounts.Login forbidden': 'Login forbidden', -}; - T9n.map('ar', require('meteor-accounts-t9n/build/ar').ar); T9n.map('ca', require('meteor-accounts-t9n/build/ca').ca); T9n.map('cs', require('meteor-accounts-t9n/build/cs').cs); @@ -51,21 +47,15 @@ T9n.map('zh-CN', require('meteor-accounts-t9n/build/zh_CN').zh_CN); T9n.map('zh-HK', require('meteor-accounts-t9n/build/zh_HK').zh_HK); T9n.map('zh-TW', require('meteor-accounts-t9n/build/zh_TW').zh_TW); -// Ensure we always have a readable message for the login-forbidden error -T9n.map('en', loginForbiddenTranslation); - // Reactively adjust useraccounts:core translations Tracker.autorun(() => { const language = TAPi18n.getLanguage(); try { T9n.setLanguage(language); - T9n.map(language, loginForbiddenTranslation); } catch (err) { // Try to extract & set the language part only (e.g. "en" instead of "en-UK") try { - const baseLanguage = language.split('-')[0]; - T9n.setLanguage(baseLanguage); - T9n.map(baseLanguage, loginForbiddenTranslation); + T9n.setLanguage(language.split('-')[0]); } catch (err) { console.error(err); } diff --git a/imports/i18n/data/ace.i18n.json b/imports/i18n/data/ace.i18n.json deleted file mode 100644 index d9ef3e7d3..000000000 --- a/imports/i18n/data/ace.i18n.json +++ /dev/null @@ -1,1686 +0,0 @@ -{ - "accept": "Accept", - "act-activity-notify": "Activity Notification", - "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createBoard": "created board __board__", - "act-createSwimlane": "created swimlane __swimlane__ to board __board__", - "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-createCustomField": "created custom field __customField__ at board __board__", - "act-deleteCustomField": "deleted custom field __customField__ at board __board__", - "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createList": "added list __list__ to board __board__", - "act-addBoardMember": "added member __member__ to board __board__", - "act-archivedBoard": "Board __board__ moved to Archive", - "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", - "act-importBoard": "imported board __board__", - "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", - "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__", - "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-removeBoardMember": "removed member __member__ from board __board__", - "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-withBoardTitle": "__board__", - "act-withCardTitle": "[__board__] __card__", - "actions": "Actions", - "activities": "Activities", - "activity": "Activity", - "activity-added": "added %s to %s", - "activity-archived": "%s moved to Archive", - "activity-attached": "attached %s to %s", - "activity-created": "created %s", - "activity-changedListTitle": "renamed list to %s", - "activity-customfield-created": "created custom field %s", - "activity-excluded": "excluded %s from %s", - "activity-imported": "imported %s into %s from %s", - "activity-imported-board": "imported %s from %s", - "activity-joined": "joined %s", - "activity-moved": "moved %s from %s to %s", - "activity-on": "on %s", - "activity-removed": "removed %s from %s", - "activity-sent": "sent %s to %s", - "activity-unjoined": "unjoined %s", - "activity-subtask-added": "added subtask to %s", - "activity-checked-item": "checked %s in checklist %s of %s", - "activity-unchecked-item": "unchecked %s in checklist %s of %s", - "activity-checklist-added": "added checklist to %s", - "activity-checklist-removed": "removed a checklist from %s", - "activity-checklist-completed": "completed checklist %s of %s", - "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", - "activity-checklist-item-added": "added checklist item to '%s' in %s", - "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", - "add": "Add", - "activity-checked-item-card": "checked %s in checklist %s", - "activity-unchecked-item-card": "unchecked %s in checklist %s", - "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "activity-checklist-uncompleted-card": "uncompleted the checklist %s", - "activity-editComment": "edited comment %s", - "activity-deleteComment": "deleted comment %s", - "activity-receivedDate": "edited received date to %s of %s", - "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", - "activity-dueDate": "edited due date to %s of %s", - "activity-endDate": "edited end date to %s of %s", - "add-attachment": "Add Attachment", - "add-board": "Add Board", - "add-template": "Add Template", - "add-card": "Add Card", - "add-card-to-top-of-list": "Add Card to Top of List", - "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", - "setListWidthPopup-title": "Set Widths", - "set-list-width": "Set Widths", - "set-list-width-value": "Set Min & Max Widths (pixels)", - "list-width-error-message": "List widths must be integers greater than 100", - "keyboard-shortcuts-enabled": "Keyboard shortcuts enabled. Click to disable.", - "keyboard-shortcuts-disabled": "Keyboard shortcuts disabled. Click to enable.", - "setSwimlaneHeightPopup-title": "Set Swimlane Height", - "set-swimlane-height": "Set Swimlane Height", - "set-swimlane-height-value": "Swimlane Height (pixels)", - "swimlane-height-error-message": "Swimlane height must be a positive integer", - "add-swimlane": "Add Swimlane", - "add-subtask": "Add Subtask", - "add-checklist": "Add Checklist", - "add-checklist-item": "Add an item to checklist", - "close-add-checklist-item": "Close add an item to checklist form", - "close-edit-checklist-item": "Close edit an item to checklist form", - "convertChecklistItemToCardPopup-title": "Convert to Card", - "add-cover": "Add cover image to minicard", - "add-label": "Add Label", - "add-list": "Add List", - "add-after-list": "Add After List", - "add-members": "Add Members", - "added": "Added", - "addMemberPopup-title": "Add Members", - "memberPopup-title": "Member Settings", - "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", - "admin-announcement": "Announcement", - "admin-announcement-active": "Active System-Wide Announcement", - "admin-announcement-title": "Announcement from Administrator", - "all-boards": "All Boards", - "and-n-other-card": "And __count__ other card", - "and-n-other-card_plural": "And __count__ other cards", - "apply": "Apply", - "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", - "app-try-reconnect": "Try to reconnect.", - "archive": "Move to Archive", - "archive-all": "Move All to Archive", - "archive-board": "Move Board to Archive", - "archive-board-confirm": "Are you sure you want to archive this board?", - "archive-card": "Move Card to Archive", - "archive-list": "Move List to Archive", - "archive-swimlane": "Move Swimlane to Archive", - "archive-selection": "Move selection to Archive", - "archiveBoardPopup-title": "Move Board to Archive?", - "archived-items": "Archive", - "archived-boards": "Boards in Archive", - "restore-board": "Restore Board", - "no-archived-boards": "No Boards in Archive.", - "archives": "Archive", - "template": "Template", - "templates": "Templates", - "template-container": "Template Container", - "add-template-container": "Add Template Container", - "assign-member": "Assign member", - "attached": "attached", - "attachment": "Attachment", - "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.", - "attachmentDeletePopup-title": "Delete Attachment?", - "attachments": "Attachments", - "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (__size__ max)", - "back": "Back", - "board-change-color": "Change color", - "board-change-background-image": "Change Background Image", - "board-background-image-url": "Background Image URL", - "add-background-image": "Add Background Image", - "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", - "boardInfoOnMyBoards-title": "All Boards Settings", - "show-card-counter-per-list": "Show card count per list", - "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", - "board-nb-stars": "%s stars", - "board-not-found": "Board not found", - "board-private-info": "This board will be private.", - "board-public-info": "This board will be public.", - "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", - "boardChangeColorPopup-title": "Change Board Background", - "boardChangeBackgroundImagePopup-title": "Change Background Image", - "allBoardsChangeColorPopup-title": "Change color", - "allBoardsChangeBackgroundImagePopup-title": "Change Background Image", - "boardChangeTitlePopup-title": "Rename Board", - "boardChangeVisibilityPopup-title": "Change Visibility", - "boardChangeWatchPopup-title": "Change Watch", - "boardMenuPopup-title": "Board Settings", - "allBoardsMenuPopup-title": "Settings", - "boardChangeViewPopup-title": "Board View", - "boards": "Boards", - "board-view": "Board View", - "desktop-mode": "Desktop Mode", - "mobile-mode": "Mobile Mode", - "mobile-desktop-toggle": "Toggle between Mobile and Desktop Mode", - "zoom-in": "Zoom In", - "zoom-out": "Zoom Out", - "click-to-change-zoom": "Click to change zoom level", - "zoom-level": "Zoom Level", - "enter-zoom-level": "Enter zoom level (50-300%):", - "board-view-cal": "Calendar", - "board-view-swimlanes": "Swimlanes", - "board-view-collapse": "Collapse", - "board-view-gantt": "Gantt", - "board-view-lists": "Lists", - "bucket-example": "Like \"Bucket List\" for example", - "calendar-previous-month-label": "Previous Month", - "calendar-next-month-label": "Next Month", - "cancel": "Cancel", - "card-archived": "This card is moved to Archive.", - "board-archived": "This board is moved to Archive.", - "card-comments-title": "This card has %s comment.", - "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.", - "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", - "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", - "card-archive-pop": "Card will not be visible at this list after archiving card.", - "card-archive-suggest-cancel": "You can later restore card from Archive.", - "card-due": "Due", - "card-due-on": "Due on", - "card-spent": "Spent Time", - "card-edit-attachments": "Edit attachments", - "card-edit-custom-fields": "Edit custom fields", - "card-edit-labels": "Edit labels", - "card-edit-members": "Edit members", - "card-labels-title": "Change the labels for the card.", - "card-members-title": "Add or remove members of the board from the card.", - "card-start": "Start", - "card-start-on": "Starts on", - "cardAttachmentsPopup-title": "Attach From", - "cardCustomField-datePopup-title": "Change date", - "cardCustomFieldsPopup-title": "Edit custom fields", - "cardStartVotingPopup-title": "Start a vote", - "positiveVoteMembersPopup-title": "Proponents", - "negativeVoteMembersPopup-title": "Opponents", - "card-edit-voting": "Edit voting", - "editVoteEndDatePopup-title": "Change vote end date", - "allowNonBoardMembers": "Allow all logged in users", - "vote-question": "Voting question", - "vote-public": "Show who voted what", - "vote-for-it": "for it", - "vote-against": "against", - "deleteVotePopup-title": "Delete vote?", - "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", - "cardStartPlanningPokerPopup-title": "Start a Planning Poker", - "card-edit-planning-poker": "Edit Planning Poker", - "editPokerEndDatePopup-title": "Change Planning Poker vote end date", - "poker-question": "Planning Poker", - "poker-one": "1", - "poker-two": "2", - "poker-three": "3", - "poker-five": "5", - "poker-eight": "8", - "poker-thirteen": "13", - "poker-twenty": "20", - "poker-forty": "40", - "poker-oneHundred": "100", - "poker-unsure": "?", - "poker-finish": "Finish", - "poker-result-votes": "Votes", - "poker-result-who": "Who", - "poker-replay": "Replay", - "set-estimation": "Set Estimation", - "deletePokerPopup-title": "Delete planning poker?", - "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", - "cardDeletePopup-title": "Delete Card?", - "cardArchivePopup-title": "Archive Card?", - "cardDetailsActionsPopup-title": "Card Actions", - "cardLabelsPopup-title": "Labels", - "cardMembersPopup-title": "Members", - "cardMorePopup-title": "More", - "cardTemplatePopup-title": "Create template", - "cards": "Cards", - "cards-count": "Cards", - "cards-count-one": "Card", - "casSignIn": "Sign In with CAS", - "cardType-card": "Card", - "cardType-linkedCard": "Linked Card", - "cardType-linkedBoard": "Linked Board", - "change": "Change", - "change-avatar": "Change Avatar", - "change-password": "Change Password", - "change-permissions": "Change permissions", - "change-settings": "Change Settings", - "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", - "changeLanguagePopup-title": "Change Language", - "changePasswordPopup-title": "Change Password", - "changePermissionsPopup-title": "Change Permissions", - "changeSettingsPopup-title": "Change Settings", - "subtasks": "Subtasks", - "checklists": "Checklists", - "click-to-star": "Click to star this board.", - "click-to-unstar": "Click to unstar this board.", - "click-to-enable-auto-width": "Auto list width disabled. Click to enable.", - "click-to-disable-auto-width": "Auto list width enabled. Click to disable.", - "auto-list-width": "Auto list width", - "clipboard": "Clipboard or drag & drop", - "close": "Close", - "close-board": "Close Board", - "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", - "close-card": "Close Card", - "color-black": "black", - "color-blue": "blue", - "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", - "color-green": "green", - "color-indigo": "indigo", - "color-lime": "lime", - "color-magenta": "magenta", - "color-mistyrose": "mistyrose", - "color-navy": "navy", - "color-orange": "orange", - "color-paleturquoise": "paleturquoise", - "color-peachpuff": "peachpuff", - "color-pink": "pink", - "color-plum": "plum", - "color-purple": "purple", - "color-red": "red", - "color-saddlebrown": "saddlebrown", - "color-silver": "silver", - "color-sky": "sky", - "color-slateblue": "slateblue", - "color-white": "white", - "color-yellow": "yellow", - "unset-color": "Unset", - "comments": "Comments", - "comment": "Comment", - "comment-placeholder": "Write Comment", - "comment-only": "Comment only", - "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", - "comment-delete": "Are you sure you want to delete the comment?", - "deleteCommentPopup-title": "Delete comment?", - "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", - "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", - "computer": "Computer", - "confirm-subtask-delete-popup": "Are you sure you want to delete subtask?", - "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", - "subtaskDeletePopup-title": "Delete Subtask?", - "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", - "copy-card-link-to-clipboard": "Copy card link to clipboard", - "copy-text-to-clipboard": "Copy text to clipboard", - "linkCardPopup-title": "Link Card", - "searchElementPopup-title": "Search", - "copyCardPopup-title": "Copy Card", - "copyManyCardsPopup-title": "Copy Template to Many Cards", - "copyManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", - "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", - "create": "Create", - "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", - "chooseBoardSourcePopup-title": "Import board", - "createLabelPopup-title": "Create Label", - "createCustomField": "Create Field", - "createCustomFieldPopup-title": "Create Field", - "current": "current", - "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", - "custom-field-checkbox": "Checkbox", - "custom-field-currency": "Currency", - "custom-field-currency-option": "Currency Code", - "custom-field-date": "Date", - "custom-field-dropdown": "Dropdown List", - "custom-field-dropdown-none": "(none)", - "custom-field-dropdown-options": "List Options", - "custom-field-dropdown-options-placeholder": "Press enter to add more options", - "custom-field-dropdown-unknown": "(unknown)", - "custom-field-number": "Number", - "custom-field-text": "Text", - "custom-fields": "Custom Fields", - "date": "Date", - "date-format": "Date Format", - "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", - "date-format-mm-dd-yyyy": "MM-DD-YYYY", - "decline": "Decline", - "default-avatar": "Default avatar", - "delete": "Delete", - "deleteCustomFieldPopup-title": "Delete Custom Field?", - "deleteLabelPopup-title": "Delete Label?", - "description": "Description", - "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", - "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", - "discard": "Discard", - "done": "Done", - "download": "Download", - "edit": "Edit", - "edit-avatar": "Change Avatar", - "edit-profile": "Edit Profile", - "edit-wip-limit": "Edit WIP Limit", - "soft-wip-limit": "Soft WIP Limit", - "editCardStartDatePopup-title": "Change start date", - "editCardDueDatePopup-title": "Change due date", - "editCustomFieldPopup-title": "Edit Field", - "addReactionPopup-title": "Add reaction", - "editCardSpentTimePopup-title": "Change spent time", - "editLabelPopup-title": "Change Label", - "editNotificationPopup-title": "Edit Notification", - "editProfilePopup-title": "Edit Profile", - "email": "Email", - "email-address": "Email Address", - "email-enrollAccount-subject": "An account created for you on __siteName__", - "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", - "email-fail": "Sending email failed", - "email-fail-text": "Error trying to send email", - "email-invalid": "Invalid email", - "email-invite": "Invite via Email", - "email-invite-subject": "__inviter__ sent you an invitation", - "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", - "email-resetPassword-subject": "Reset your password on __siteName__", - "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", - "email-sent": "Email sent", - "email-verifyEmail-subject": "Verify your email address on __siteName__", - "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", - "enable-vertical-scrollbars": "Enable vertical scrollbars", - "enable-wip-limit": "Enable WIP Limit", - "error-board-doesNotExist": "This board does not exist", - "error-board-notAdmin": "You need to be admin of this board to do that", - "error-board-notAMember": "You need to be a member of this board to do that", - "error-json-malformed": "Your text is not valid JSON", - "error-json-schema": "Your JSON data does not include the proper information in the correct format", - "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format ", - "error-list-doesNotExist": "This list does not exist", - "error-user-doesNotExist": "This user does not exist", - "error-user-notAllowSelf": "You can not invite yourself", - "error-user-notCreated": "This user is not created", - "error-username-taken": "This username is already taken", - "error-orgname-taken": "This organization name is already taken", - "error-teamname-taken": "This team name is already taken", - "error-email-taken": "Email has already been taken", - "export-board": "Export board", - "export-board-json": "Export board to JSON", - "export-board-csv": "Export board to CSV", - "export-board-tsv": "Export board to TSV", - "export-board-excel": "Export board to Excel", - "user-can-not-export-excel": "User can not export Excel", - "export-board-html": "Export board to HTML", - "export-card": "Export card", - "export-card-pdf": "Export card to PDF", - "user-can-not-export-card-to-pdf": "User can not export card to PDF", - "exportBoardPopup-title": "Export board", - "exportCardPopup-title": "Export card", - "sort": "Sort", - "sorted": "Sorted", - "remove-sort": "Remove sort", - "sort-desc": "Click to Sort List", - "list-sort-by": "Sort the List By:", - "list-label-modifiedAt": "Last Access Time", - "list-label-title": "Name of the List", - "list-label-sort": "Your Manual Order", - "list-label-short-modifiedAt": "(L)", - "list-label-short-title": "(N)", - "list-label-short-sort": "(M)", - "filter": "Filter", - "filter-cards": "Filter Cards or Lists", - "filter-dates-label": "Filter by date", - "filter-no-due-date": "No due date", - "filter-overdue": "Overdue", - "filter-due-today": "Due today", - "filter-due-this-week": "Due this week", - "filter-due-next-week": "Due next week", - "filter-due-tomorrow": "Due tomorrow", - "list-filter-label": "Filter List by Title", - "filter-clear": "Clear filter", - "filter-labels-label": "Filter by label", - "filter-no-label": "No label", - "filter-member-label": "Filter by member", - "filter-no-member": "No member", - "filter-assignee-label": "Filter by assignee", - "filter-no-assignee": "No assignee", - "filter-custom-fields-label": "Filter by Custom Fields", - "filter-no-custom-fields": "No Custom Fields", - "filter-show-archive": "Show archived lists", - "filter-hide-empty": "Hide empty lists", - "filter-on": "Filter is on", - "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", - "filter-to-selection": "Filter to selection", - "other-filters-label": "Other Filters", - "advanced-filter-label": "Advanced Filter", - "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", - "fullname": "Full Name", - "header-logo-title": "Go back to your boards page.", - "show-activities": "Show Activities", - "headerBarCreateBoardPopup-title": "Create Board", - "home": "Home", - "import": "Import", - "impersonate-user": "Impersonate user", - "link": "Link", - "import-board": "import board", - "import-board-c": "Import board", - "import-board-title-trello": "Import board from Trello", - "import-board-title-wekan": "Import board from previous export", - "import-board-title-csv": "Import board from CSV/TSV", - "from-trello": "From Trello", - "from-wekan": "From previous export", - "from-csv": "From CSV/TSV", - "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", - "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", - "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", - "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", - "import-json-placeholder": "Paste your valid JSON data here", - "import-csv-placeholder": "Paste your valid CSV/TSV data here", - "import-map-members": "Map members", - "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", - "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", - "import-show-user-mapping": "Review members mapping", - "import-user-select": "Pick your existing user you want to use as this member", - "importMapMembersAddPopup-title": "Select member", - "info": "Version", - "initials": "Initials", - "invalid-date": "Invalid date", - "invalid-time": "Invalid time", - "invalid-user": "Invalid user", - "joined": "joined", - "just-invited": "You are just invited to this board", - "keyboard-shortcuts": "Keyboard shortcuts", - "label-create": "Create Label", - "label-default": "%s label (default)", - "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", - "labels": "Labels", - "language": "Language", - "last-admin-desc": "You can’t change roles because there must be at least one admin.", - "leave-board": "Leave Board", - "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", - "leaveBoardPopup-title": "Leave Board ?", - "link-card": "Link to this card", - "list-archive-cards": "Move all cards in this list to Archive", - "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", - "list-move-cards": "Move all cards in this list", - "list-select-cards": "Select all cards in this list", - "set-color-list": "Set Color", - "listActionPopup-title": "List Actions", - "settingsUserPopup-title": "User Settings", - "settingsTeamPopup-title": "Team Settings", - "settingsOrgPopup-title": "Organization Settings", - "swimlaneActionPopup-title": "Swimlane Actions", - "swimlaneAddPopup-title": "Add a Swimlane below", - "listImportCardPopup-title": "Import a Trello card", - "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", - "listMorePopup-title": "More", - "link-list": "Link to this list", - "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", - "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", - "lists": "Lists", - "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", - "log-out": "Log Out", - "log-in": "Log In", - "loginPopup-title": "Log In", - "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", - "members": "Members", - "menu": "Menu", - "move-selection": "Move selection", - "moveCardPopup-title": "Move Card", - "moveCardToBottom-title": "Move to Bottom", - "moveCardToTop-title": "Move to Top", - "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", - "multi-selection": "Multi-Selection", - "multi-selection-label": "Set label for selection", - "multi-selection-member": "Set member for selection", - "multi-selection-on": "Multi-Selection is on", - "muted": "Muted", - "muted-info": "You will never be notified of any changes in this board", - "my-boards": "My Boards", - "name": "Name", - "no-archived-cards": "No cards in Archive.", - "no-archived-lists": "No lists in Archive.", - "no-archived-swimlanes": "No swimlanes in Archive.", - "no-results": "No results", - "normal": "Normal", - "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", - "not-accepted-yet": "Invitation not accepted yet", - "notify-participate": "Receive updates to any cards you participate as creator or member", - "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", - "optional": "optional", - "or": "or", - "page-maybe-private": "This page may be private. You may be able to view it by logging in.", - "page-not-found": "Page not found.", - "password": "Password", - "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", - "participating": "Participating", - "preview": "Preview", - "previewAttachedImagePopup-title": "Preview", - "previewClipboardImagePopup-title": "Preview", - "private": "Private", - "private-desc": "This board is private. Only people added to the board can view and edit it.", - "profile": "Profile", - "public": "Public", - "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", - "quick-access-description": "Star a board to add a shortcut in this bar.", - "remove-cover": "Remove cover image from minicard", - "remove-from-board": "Remove from Board", - "remove-label": "Remove Label", - "listDeletePopup-title": "Delete List ?", - "remove-member": "Remove Member", - "remove-member-from-card": "Remove from Card", - "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", - "removeMemberPopup-title": "Remove Member?", - "rename": "Rename", - "rename-board": "Rename Board", - "restore": "Restore", - "rescue-card-description": "Show rescue dialogue before closing for unsaved card descriptions", - "rescue-card-description-dialogue": "Overwrite current card description with your changes?", - "save": "Save", - "search": "Search", - "rules": "Rules", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Write text you search and press Enter", - "select-color": "Select Color", - "select-board": "Select Board", - "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", - "setWipLimitPopup-title": "Set WIP Limit", - "shortcut-add-self": "Add yourself to current card", - "shortcut-assign-self": "Assign yourself to current card", - "shortcut-autocomplete-emoji": "Autocomplete emoji", - "shortcut-autocomplete-members": "Autocomplete members", - "shortcut-clear-filters": "Clear all filters", - "shortcut-close-dialog": "Close Dialog", - "shortcut-filter-my-cards": "Filter my cards", - "shortcut-filter-my-assigned-cards": "Filter my assigned cards", - "shortcut-show-shortcuts": "Bring up this shortcuts list", - "shortcut-toggle-filterbar": "Toggle Filter Sidebar", - "shortcut-toggle-searchbar": "Toggle Search Sidebar", - "shortcut-toggle-sidebar": "Toggle Board Sidebar", - "show-cards-minimum-count": "Show cards count if list contains more than", - "sidebar-open": "Open Sidebar", - "sidebar-close": "Close Sidebar", - "signupPopup-title": "Create an Account", - "star-board-title": "Click to star this board. It will show up at top of your boards list.", - "starred-boards": "Starred Boards", - "starred-boards-description": "Starred boards show up at the top of your boards list.", - "subscribe": "Subscribe", - "team": "Team", - "this-board": "this board", - "this-card": "this card", - "spent-time-hours": "Spent time (hours)", - "overtime-hours": "Overtime (hours)", - "overtime": "Overtime", - "has-overtime-cards": "Has overtime cards", - "has-spenttime-cards": "Has spent time cards", - "time": "Time", - "title": "Title", - "toggle-assignees": "Toggle assignees 1-9 for card (By order of addition to board).", - "toggle-labels": "Toggle labels 1-9 for card. Multi-Selection adds labels 1-9", - "remove-labels-multiselect": "Multi-Selection removes labels 1-9", - "tracking": "Tracking", - "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", - "type": "Type", - "unassign-member": "Unassign member", - "unsaved-description": "You have an unsaved description.", - "unwatch": "Unwatch", - "upload": "Upload", - "upload-avatar": "Upload an avatar", - "uploaded-avatar": "Uploaded an avatar", - "uploading-files": "Uploading files", - "upload-failed": "Upload failed", - "upload-completed": "Upload completed", - "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", - "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", - "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", - "custom-login-logo-image-url": "Custom Login Logo Image URL", - "custom-login-logo-link-url": "Custom Login Logo Link URL", - "custom-help-link-url": "Custom Help Link URL", - "text-below-custom-login-logo": "Text below Custom Login Logo", - "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", - "username": "Username", - "import-usernames": "Import Usernames", - "view-it": "View it", - "warn-list-archived": "warning: this card is in an list at Archive", - "watch": "Watch", - "watching": "Watching", - "watching-info": "You will be notified of any change in this board", - "welcome-board": "Welcome Board", - "welcome-swimlane": "Milestone 1", - "welcome-list1": "Basics", - "welcome-list2": "Advanced", - "card-templates-swimlane": "Card Templates", - "list-templates-swimlane": "List Templates", - "board-templates-swimlane": "Board Templates", - "what-to-do": "What do you want to do?", - "wipLimitErrorPopup-title": "Invalid WIP Limit", - "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", - "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", - "admin-panel": "Admin Panel", - "settings": "Settings", - "people": "People", - "registration": "Registration", - "disable-self-registration": "Disable Self-Registration", - "disable-forgot-password": "Disable Forgot Password", - "invite": "Invite", - "invite-people": "Invite People", - "to-boards": "To board(s)", - "email-addresses": "Email Addresses", - "smtp-host-description": "The address of the SMTP server that handles your emails.", - "smtp-port-description": "The port your SMTP server uses for outgoing emails.", - "smtp-tls-description": "Enable TLS support for SMTP server", - "smtp-host": "SMTP Host", - "smtp-port": "SMTP Port", - "smtp-username": "Username", - "smtp-password": "Password", - "smtp-tls": "TLS support", - "send-from": "From", - "send-smtp-test": "Send a test email to yourself", - "invitation-code": "Invitation Code", - "email-invite-register-subject": "__inviter__ sent you an invitation", - "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", - "email-smtp-test-subject": "SMTP Test Email", - "email-smtp-test-text": "You have successfully sent an email", - "error-invitation-code-not-exist": "Invitation code doesn't exist", - "error-notAuthorized": "You are not authorized to view this page.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional for Authentication)", - "outgoing-webhooks": "Outgoing Webhooks", - "bidirectional-webhooks": "Two-Way Webhooks", - "outgoingWebhooksPopup-title": "Outgoing Webhooks", - "boardCardTitlePopup-title": "Card Title Filter", - "disable-webhook": "Disable This Webhook", - "global-webhook": "Global Webhooks", - "new-outgoing-webhook": "New Outgoing Webhook", - "no-name": "(Unknown)", - "Node_version": "Node version", - "Meteor_version": "Meteor version", - "MongoDB_version": "MongoDB version", - "MongoDB_storage_engine": "MongoDB storage engine", - "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", - "OS_Arch": "OS Arch", - "OS_Cpus": "OS CPU Count", - "OS_Freemem": "OS Free Memory", - "OS_Loadavg": "OS Load Average", - "OS_Platform": "OS Platform", - "OS_Release": "OS Release", - "OS_Totalmem": "OS Total Memory", - "OS_Type": "OS Type", - "OS_Uptime": "OS Uptime", - "days": "days", - "hours": "hours", - "minutes": "minutes", - "seconds": "seconds", - "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Add field to new cards", - "always-field-on-card": "Add field to all cards", - "showLabel-field-on-card": "Show field label on minicard", - "showSum-field-on-list": "Show sum of fields at top of list", - "yes": "Yes", - "no": "No", - "accounts": "Accounts", - "accounts-allowEmailChange": "Allow Email Change", - "accounts-allowUserNameChange": "Allow Username Change", - "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", - "createdAt": "Created at", - "modifiedAt": "Modified at", - "verified": "Verified", - "active": "Active", - "card-received": "Received", - "card-received-on": "Received on", - "card-end": "End", - "card-end-on": "Ends on", - "editCardReceivedDatePopup-title": "Change received date", - "editCardEndDatePopup-title": "Change end date", - "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", - "assigned-by": "Assigned By", - "requested-by": "Requested By", - "card-sorting-by-number": "Card sorting by number", - "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", - "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", - "boardDeletePopup-title": "Delete Board?", - "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", - "delete-duplicate-lists": "Delete Duplicate Lists", - "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", - "default-subtasks-board": "Subtasks for __board__ board", - "default": "Default", - "defaultdefault": "Default", - "queue": "Queue", - "subtask-settings": "Subtasks Settings", - "card-settings": "Card Settings", - "minicard-settings": "Minicard Settings", - "boardSubtaskSettingsPopup-title": "Subtasks Settings", - "boardCardSettingsPopup-title": "Card Settings", - "boardMinicardSettingsPopup-title": "Minicard Settings", - "deposit-subtasks-board": "Deposit subtasks to this board:", - "deposit-subtasks-list": "Landing list for subtasks deposited here:", - "show-parent-in-minicard": "Show parent in minicard:", - "description-on-minicard": "Description on minicard", - "cover-attachment-on-minicard": "Cover image on minicard", - "badge-attachment-on-minicard": "Count of attachments on minicard", - "card-sorting-by-number-on-minicard": "Card sorting by number on minicard", - "prefix-with-full-path": "Prefix with full path", - "prefix-with-parent": "Prefix with parent", - "subtext-with-full-path": "Subtext with full path", - "subtext-with-parent": "Subtext with parent", - "change-card-parent": "Change card's parent", - "parent-card": "Parent card", - "source-board": "Source board", - "no-parent": "Don't show parent", - "activity-added-label": "added label '%s' to %s", - "activity-removed-label": "removed label '%s' from %s", - "activity-delete-attach": "deleted an attachment from %s", - "activity-added-label-card": "added label '%s'", - "activity-removed-label-card": "removed label '%s'", - "activity-delete-attach-card": "deleted an attachment", - "activity-set-customfield": "set custom field '%s' to '%s' in %s", - "activity-unset-customfield": "unset custom field '%s' in %s", - "r-rule": "Rule", - "r-add-trigger": "Add trigger", - "r-add-action": "Add action", - "r-board-rules": "Board rules", - "r-add-rule": "Add rule", - "r-view-rule": "View rule", - "r-delete-rule": "Delete rule", - "r-new-rule-name": "New rule title", - "r-no-rules": "No rules", - "r-trigger": "Trigger", - "r-action": "Action", - "r-when-a-card": "When a card", - "r-is": "is", - "r-is-moved": "is moved", - "r-added-to": "Added to", - "r-removed-from": "Removed from", - "r-the-board": "the board", - "r-list": "list", - "set-filter": "Set Filter", - "r-moved-to": "Moved to", - "r-moved-from": "Moved from", - "r-archived": "Moved to Archive", - "r-unarchived": "Restored from Archive", - "r-a-card": "a card", - "r-when-a-label-is": "When a label is", - "r-when-the-label": "When the label", - "r-list-name": "list name", - "r-when-a-member": "When a member is", - "r-when-the-member": "When the member", - "r-name": "name", - "r-when-a-attach": "When an attachment", - "r-when-a-checklist": "When a checklist is", - "r-when-the-checklist": "When the checklist", - "r-completed": "Completed", - "r-made-incomplete": "Made incomplete", - "r-when-a-item": "When a checklist item is", - "r-when-the-item": "When the checklist item", - "r-checked": "Checked", - "r-unchecked": "Unchecked", - "r-move-card-to": "Move card to", - "r-top-of": "Top of", - "r-bottom-of": "Bottom of", - "r-its-list": "its list", - "r-archive": "Move to Archive", - "r-unarchive": "Restore from Archive", - "r-card": "card", - "r-add": "Add", - "r-remove": "Remove", - "r-label": "label", - "r-member": "member", - "r-remove-all": "Remove all members from the card", - "r-set-color": "Set color to", - "r-checklist": "checklist", - "r-check-all": "Check all", - "r-uncheck-all": "Uncheck all", - "r-items-check": "items of checklist", - "r-check": "Check", - "r-uncheck": "Uncheck", - "r-item": "item", - "r-of-checklist": "of checklist", - "r-send-email": "Send an email", - "r-to": "to", - "r-of": "of", - "r-subject": "subject", - "r-rule-details": "Rule details", - "r-d-move-to-top-gen": "Move card to top of its list", - "r-d-move-to-top-spec": "Move card to top of list", - "r-d-move-to-bottom-gen": "Move card to bottom of its list", - "r-d-move-to-bottom-spec": "Move card to bottom of list", - "r-d-send-email": "Send email", - "r-d-send-email-to": "to", - "r-d-send-email-subject": "subject", - "r-d-send-email-message": "message", - "r-d-archive": "Move card to Archive", - "r-d-unarchive": "Restore card from Archive", - "r-d-add-label": "Add label", - "r-d-remove-label": "Remove label", - "r-create-card": "Create new card", - "r-in-list": "in list", - "r-in-swimlane": "in swimlane", - "r-d-add-member": "Add member", - "r-d-remove-member": "Remove member", - "r-d-remove-all-member": "Remove all member", - "r-d-check-all": "Check all items of a list", - "r-d-uncheck-all": "Uncheck all items of a list", - "r-d-check-one": "Check item", - "r-d-uncheck-one": "Uncheck item", - "r-d-check-of-list": "of checklist", - "r-d-add-checklist": "Add checklist", - "r-d-remove-checklist": "Remove checklist", - "r-by": "by", - "r-add-checklist": "Add checklist", - "r-with-items": "with items", - "r-items-list": "item1,item2,item3", - "r-add-swimlane": "Add swimlane", - "r-swimlane-name": "swimlane name", - "r-board-note": "Note: leave a field empty to match every possible value. ", - "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", - "r-when-a-card-is-moved": "When a card is moved to another list", - "r-set": "Set", - "r-update": "Update", - "r-datefield": "date field", - "r-df-start-at": "start", - "r-df-due-at": "due", - "r-df-end-at": "end", - "r-df-received-at": "received", - "r-to-current-datetime": "to current date/time", - "r-remove-value-from": "Remove value from", - "r-link-card": "Link card to", - "ldap": "LDAP", - "oauth2": "OAuth2", - "cas": "CAS", - "authentication-method": "Authentication method", - "authentication-type": "Authentication type", - "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", - "layout": "Layout", - "hide-logo": "Hide Logo", - "hide-card-counter-list": "Hide card counter list on All Boards", - "hide-board-member-list": "Hide board member list on All Boards", - "add-custom-html-after-body-start": "Add Custom HTML after start", - "add-custom-html-before-body-end": "Add Custom HTML before end", - "error-undefined": "Something went wrong", - "error-ldap-login": "An error occurred while trying to login", - "display-authentication-method": "Display Authentication Method", - "oidc-button-text": "Customize the OIDC button text", - "default-authentication-method": "Default Authentication Method", - "duplicate-board": "Duplicate Board", - "duplicate-board-confirm": "Are you sure you want to duplicate this board?", - "org-number": "The number of organizations is: ", - "team-number": "The number of teams is: ", - "people-number": "The number of people is: ", - "swimlaneDeletePopup-title": "Delete Swimlane ?", - "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", - "restore-all": "Restore all", - "delete-all": "Delete all", - "loading": "Loading, please wait.", - "previous_as": "last time was", - "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", - "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", - "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", - "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", - "a-dueAt": "modified due time to be", - "a-endAt": "modified ending time to be", - "a-startAt": "modified starting time to be", - "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", - "almostdue": "current due time %s is approaching", - "pastdue": "current due time %s is past", - "duenow": "current due time %s is today", - "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", - "act-withDue": "__list__/__card__ due reminders [__board__]", - "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", - "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", - "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", - "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", - "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", - "accounts-allowUserDelete": "Allow users to self delete their account", - "hide-minicard-label-text": "Hide minicard label text", - "show-desktop-drag-handles": "Show desktop drag handles", - "assignee": "Assignee", - "cardAssigneesPopup-title": "Assignee", - "addmore-detail": "Add a more detailed description", - "show-on-card": "Show on Card", - "show-on-minicard": "Show on Minicard", - "new": "New", - "editOrgPopup-title": "Edit Organization", - "newOrgPopup-title": "New Organization", - "editTeamPopup-title": "Edit Team", - "newTeamPopup-title": "New Team", - "editUserPopup-title": "Edit User", - "newUserPopup-title": "New User", - "notifications": "Notifications", - "help": "Help", - "view-all": "View All", - "filter-by-unread": "Filter by Unread", - "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", - "remove-all-read": "Remove all read", - "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename", - "start-day-of-week": "Set day of the week start", - "monday": "Monday", - "tuesday": "Tuesday", - "wednesday": "Wednesday", - "thursday": "Thursday", - "friday": "Friday", - "saturday": "Saturday", - "sunday": "Sunday", - "status": "Status", - "swimlane": "Swimlane", - "owner": "Owner", - "last-modified-at": "Last modified at", - "last-activity": "Last activity", - "voting": "Voting", - "archived": "Archived", - "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", - "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", - "hide-checked-items": "Hide checked items", - "hide-finished-checklist": "Hide finished checklist", - "task": "Task", - "create-task": "Create Task", - "ok": "OK", - "organizations": "Organizations", - "teams": "Teams", - "displayName": "Display Name", - "shortName": "Short Name", - "autoAddUsersWithDomainName": "Automatically add users with the domain name", - "website": "Website", - "person": "Person", - "my-cards": "My Cards", - "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", - "list": "List", - "board": "Board", - "context-separator": "/", - "myCardsViewChange-title": "My Cards View", - "myCardsViewChangePopup-title": "My Cards View", - "myCardsViewChange-choice-boards": "Boards", - "myCardsViewChange-choice-table": "Table", - "myCardsSortChange-title": "My Cards Sort", - "myCardsSortChangePopup-title": "My Cards Sort", - "myCardsSortChange-choice-board": "By Board", - "myCardsSortChange-choice-dueat": "By Due Date", - "dueCards-title": "Due Cards", - "dueCardsViewChange-title": "Due Cards View", - "dueCardsViewChangePopup-title": "Due Cards View", - "dueCardsViewChange-choice-me": "Me", - "dueCardsViewChange-choice-all": "All Users", - "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", - "dueCards-noResults-title": "No Due Cards Found", - "dueCards-noResults-description": "You don't have any cards with due dates at the moment.", - "broken-cards": "Broken Cards", - "board-title-not-found": "Board '%s' not found.", - "swimlane-title-not-found": "Swimlane '%s' not found.", - "list-title-not-found": "List '%s' not found.", - "label-not-found": "Label '%s' not found.", - "label-color-not-found": "Label color %s not found.", - "user-username-not-found": "Username '%s' not found.", - "comment-not-found": "Card with comment containing text '%s' not found.", - "org-name-not-found": "Organization '%s' not found.", - "team-name-not-found": "Team '%s' not found.", - "globalSearch-title": "Search All Boards", - "no-cards-found": "No Cards Found", - "one-card-found": "One Card Found", - "n-cards-found": "%s Cards Found", - "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", - "operator-board": "board", - "operator-board-abbrev": "b", - "operator-swimlane": "swimlane", - "operator-swimlane-abbrev": "s", - "operator-list": "list", - "operator-list-abbrev": "l", - "operator-label": "label", - "operator-label-abbrev": "#", - "operator-user": "user", - "operator-user-abbrev": "@", - "operator-member": "member", - "operator-member-abbrev": "m", - "operator-assignee": "assignee", - "operator-assignee-abbrev": "a", - "operator-creator": "creator", - "operator-status": "status", - "operator-due": "due", - "operator-created": "created", - "operator-modified": "modified", - "operator-sort": "sort", - "operator-comment": "comment", - "operator-has": "has", - "operator-limit": "limit", - "operator-debug": "debug", - "operator-org": "org", - "operator-team": "team", - "predicate-archived": "archived", - "predicate-open": "open", - "predicate-ended": "ended", - "predicate-all": "all", - "predicate-overdue": "overdue", - "predicate-week": "week", - "predicate-month": "month", - "predicate-quarter": "quarter", - "predicate-year": "year", - "predicate-due": "due", - "predicate-modified": "modified", - "predicate-created": "created", - "predicate-attachment": "attachment", - "predicate-description": "description", - "predicate-checklist": "checklist", - "predicate-start": "start", - "predicate-end": "end", - "predicate-assignee": "assignee", - "predicate-member": "member", - "predicate-public": "public", - "predicate-private": "private", - "predicate-selector": "selector", - "predicate-projection": "projection", - "operator-unknown-error": "%s is not an operator", - "operator-number-expected": "operator __operator__ expected a number, got '__value__'", - "operator-sort-invalid": "sort of '%s' is invalid", - "operator-status-invalid": "'%s' is not a valid status", - "operator-has-invalid": "%s is not a valid existence check", - "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", - "operator-debug-invalid": "%s is not a valid debug predicate", - "next-page": "Next Page", - "previous-page": "Previous Page", - "heading-notes": "Notes", - "globalSearch-instructions-heading": "Search Instructions", - "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", - "globalSearch-instructions-operators": "Available operators:", - "globalSearch-instructions-operator-board": "`__operator_board__:` - cards in boards matching the specified *<title>*", - "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", - "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", - "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", - "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", - "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", - "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", - "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", - "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", - "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", - "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", - "globalSearch-instructions-operator-org": "`__operator_org__:<display name|short name>` - cards belonging to a board assigned to organization *<name>*", - "globalSearch-instructions-operator-team": "`__operator_team__:<display name|short name>` - cards belonging to a board assigned to team *<name>*", - "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", - "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", - "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", - "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", - "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", - "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", - "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", - "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", - "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", - "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", - "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", - "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", - "globalSearch-instructions-notes-1": "Multiple operators may be specified.", - "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", - "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", - "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", - "globalSearch-instructions-notes-4": "Text searches are case insensitive.", - "globalSearch-instructions-notes-5": "By default archived cards are not searched.", - "link-to-search": "Link to this search", - "excel-font": "Arial", - "number": "Number", - "label-colors": "Label Colors", - "label-names": "Label Names", - "archived-at": "archived at", - "sort-cards": "Sort Cards", - "sort-is-on": "Sort is on", - "cardsSortPopup-title": "Sort Cards", - "due-date": "Due Date", - "server-error": "Server Error", - "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", - "title-alphabetically": "Title (Alphabetically)", - "created-at-newest-first": "Created At (Newest First)", - "created-at-oldest-first": "Created At (Oldest First)", - "links-heading": "Links", - "hide-activities-of-all-boards": "Don't show the board activities on all boards", - "now-activities-of-all-boards-are-hidden": "Now all activities of all boards are hidden", - "move-swimlane": "Move Swimlane", - "moveSwimlanePopup-title": "Move Swimlane", - "custom-field-stringtemplate": "String Template", - "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", - "custom-field-stringtemplate-separator": "Separator (use or   for a space)", - "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", - "creator": "Creator", - "creator-on-minicard": "Creator on minicard", - "filesReportTitle": "Files Report", - "reports": "Reports", - "rulesReportTitle": "Rules Report", - "boardsReportTitle": "Boards Report", - "cardsReportTitle": "Cards Report", - "copy-swimlane": "Copy Swimlane", - "copySwimlanePopup-title": "Copy Swimlane", - "display-card-creator": "Display Card Creator", - "wait-spinner": "Wait Spinner", - "Bounce": "Bounce Wait Spinner", - "Cube": "Cube Wait Spinner", - "Cube-Grid": "Cube-Grid Wait Spinner", - "Dot": "Dot Wait Spinner", - "Double-Bounce": "Double Bounce Wait Spinner", - "Rotateplane": "Rotateplane Wait Spinner", - "Scaleout": "Scaleout Wait Spinner", - "Wave": "Wave Wait Spinner", - "maximize-card": "Maximize Card", - "minimize-card": "Minimize Card", - "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", - "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it", - "subject": "Subject", - "details": "Details", - "carbon-copy": "Carbon Copy (Cc:)", - "ticket": "Ticket", - "tickets": "Tickets", - "ticket-number": "Ticket Number", - "open": "Open", - "pending": "Pending", - "closed": "Closed", - "resolved": "Resolved", - "cancelled": "Cancelled", - "history": "History", - "request": "Request", - "requests": "Requests", - "help-request": "Help Request", - "editCardSortOrderPopup-title": "Change Sorting", - "cardDetailsPopup-title": "Card Details", - "add-teams": "Add teams", - "add-teams-label": "Added teams are displayed below:", - "remove-team-from-table": "Are you sure you want to remove this team from the board ?", - "confirm-btn": "Confirm", - "remove-btn": "Remove", - "filter-card-title-label": "Filter by card title", - "invite-people-success": "Invitation to register sent with success", - "invite-people-error": "Error while sending invitation to register", - "can-invite-if-same-mailDomainName": "Email domain name", - "to-create-teams-contact-admin": "To create teams, please contact the administrator.", - "Node_heap_total_heap_size": "Node heap: total heap size", - "Node_heap_total_heap_size_executable": "Node heap: total heap size executable", - "Node_heap_total_physical_size": "Node heap: total physical size", - "Node_heap_total_available_size": "Node heap: total available size", - "Node_heap_used_heap_size": "Node heap: used heap size", - "Node_heap_heap_size_limit": "Node heap: heap size limit", - "Node_heap_malloced_memory": "Node heap: malloced memory", - "Node_heap_peak_malloced_memory": "Node heap: peak malloced memory", - "Node_heap_does_zap_garbage": "Node heap: does zap garbage", - "Node_heap_number_of_native_contexts": "Node heap: number of native contexts", - "Node_heap_number_of_detached_contexts": "Node heap: number of detached contexts", - "Node_memory_usage_rss": "Node memory usage: resident set size", - "Node_memory_usage_heap_total": "Node memory usage: total size of the allocated heap", - "Node_memory_usage_heap_used": "Node memory usage: actual memory used", - "Node_memory_usage_external": "Node memory usage: external", - "add-organizations": "Add organizations", - "add-organizations-label": "Added organizations are displayed below:", - "remove-organization-from-board": "Are you sure you want to remove this organization from this board ?", - "to-create-organizations-contact-admin": "To create organizations, please contact administrator.", - "custom-legal-notice-link-url": "Custom legal notice page URL", - "acceptance_of_our_legalNotice": "By continuing, you accept our", - "legalNotice": "legal notice", - "copied": "Copied!", - "checklistActionsPopup-title": "Checklist Actions", - "moveChecklist": "Move Checklist", - "moveChecklistPopup-title": "Move Checklist", - "newlineBecomesNewChecklistItem": "Each line of text becomes one of the checklist items", - "newLineNewItem": "One line of text = one checklist item", - "newlineBecomesNewChecklistItemOriginOrder": "Each line of text becomes one of the checklist items, original order", - "originOrder": "original order", - "copyChecklist": "Copy Checklist", - "copyChecklistPopup-title": "Copy Checklist", - "card-show-lists": "Card Show Lists", - "subtaskActionsPopup-title": "Subtask Actions", - "attachmentActionsPopup-title": "Attachment Actions", - "attachment-move-storage-fs": "Move attachment to filesystem", - "attachment-move-storage-gridfs": "Move attachment to GridFS", - "attachment-move-storage-s3": "Move attachment to S3", - "attachment-move": "Move Attachment", - "move-all-attachments-to-fs": "Move all attachments to filesystem", - "move-all-attachments-to-gridfs": "Move all attachments to GridFS", - "move-all-attachments-to-s3": "Move all attachments to S3", - "move-all-attachments-of-board-to-fs": "Move all attachments of board to filesystem", - "move-all-attachments-of-board-to-gridfs": "Move all attachments of board to GridFS", - "move-all-attachments-of-board-to-s3": "Move all attachments of board to S3", - "path": "Path", - "version-name": "Version-Name", - "size": "Size", - "storage": "Storage", - "action": "Action", - "board-title": "Board Title", - "attachmentRenamePopup-title": "Rename", - "uploading": "Uploading", - "remaining_time": "Remaining time", - "speed": "Speed", - "progress": "Progress", - "password-again": "Password (again)", - "if-you-already-have-an-account": "If you already have an account", - "register": "Register", - "forgot-password": "Forgot password", - "minicardDetailsActionsPopup-title": "Card Details", - "Mongo_sessions_count": "Mongo sessions count", - "change-visibility": "Change Visibility", - "max-upload-filesize": "Max upload filesize in bytes:", - "allowed-upload-filetypes": "Allowed upload filetypes:", - "max-avatar-filesize": "Max avatar filesize in bytes:", - "allowed-avatar-filetypes": "Allowed avatar filetypes:", - "invalid-file": "If filename is invalid, upload or rename is cancelled.", - "preview-pdf-not-supported": "Your device does not support previewing PDF. Try downloading instead.", - "drag-board": "Drag board", - "translation-number": "The number of custom translation strings is:", - "delete-translation-confirm-popup": "Are you sure you want to delete this custom translation string? There is no undo.", - "newTranslationPopup-title": "New custom translation string", - "editTranslationPopup-title": "Edit custom translation string", - "settingsTranslationPopup-title": "Delete this custom translation string?", - "translation": "Translation", - "text": "Text", - "translation-text": "Translation text", - "show-subtasks-field": "Show subtasks field", - "show-week-of-year": "Show week of year (ISO 8601)", - "convert-to-markdown": "Convert to markdown", - "import-board-zip": "Add .zip file that has board JSON files, and board name subdirectories with attachments", - "collapse": "Collapse", - "uncollapse": "Uncollapse", - "hideCheckedChecklistItems": "Hide checked checklist items", - "hideAllChecklistItems": "Hide all checklist items", - "support": "Support", - "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", - "accessibility": "Accessibility", - "accessibility-page-enabled": "Accessibility page enabled", - "accessibility-info-not-added-yet": "Accessibility info has not been added yet", - "accessibility-title": "Accessibility title", - "accessibility-content": "Accessibility content", - "accounts-lockout-settings": "Brute Force Protection Settings", - "accounts-lockout-info": "These settings control how login attempts are protected against brute force attacks.", - "accounts-lockout-known-users": "Settings for known users (correct username, wrong password)", - "accounts-lockout-unknown-users": "Settings for unknown users (non-existent username)", - "accounts-lockout-failures-before": "Failures before lockout", - "accounts-lockout-period": "Lockout period (seconds)", - "accounts-lockout-failure-window": "Failure window (seconds)", - "accounts-lockout-settings-updated": "Brute force protection settings have been updated", - "accounts-lockout-locked-users": "Locked Users", - "accounts-lockout-locked-users-info": "Users currently locked out due to too many failed login attempts", - "accounts-lockout-no-locked-users": "There are currently no locked users", - "accounts-lockout-failed-attempts": "Failed Attempts", - "accounts-lockout-remaining-time": "Remaining Time", - "accounts-lockout-user-unlocked": "User has been unlocked successfully", - "accounts-lockout-confirm-unlock": "Are you sure you want to unlock this user?", - "accounts-lockout-confirm-unlock-all": "Are you sure you want to unlock all locked users?", - "accounts-lockout-show-locked-users": "Show locked users only", - "accounts-lockout-user-locked": "User is locked", - "accounts-lockout-click-to-unlock": "Click to unlock this user", - "accounts-lockout-status": "Status", - "admin-people-filter-show": "Show:", - "admin-people-filter-all": "All Users", - "admin-people-filter-locked": "Locked Users Only", - "admin-people-filter-active": "Active", - "admin-people-filter-inactive": "Not Active", - "admin-people-active-status": "Active Status", - "admin-people-user-active": "User is active - click to deactivate", - "admin-people-user-inactive": "User is inactive - click to activate", - "accounts-lockout-all-users-unlocked": "All locked users have been unlocked", - "accounts-lockout-unlock-all": "Unlock All", - "active-cron-jobs": "Active Scheduled Jobs", - "add-cron-job": "Add Scheduled Job", - "add-cron-job-placeholder": "Add Scheduled Job functionality coming soon", - "attachment-storage-configuration": "Attachment Storage Configuration", - "attachments-path": "Attachments Path", - "attachments-path-description": "Path where attachment files are stored", - "avatars-path": "Avatars Path", - "avatars-path-description": "Path where avatar files are stored", - "board-archive-failed": "Failed to schedule board archive", - "board-archive-scheduled": "Board archive scheduled successfully", - "board-backup-failed": "Failed to schedule board backup", - "board-backup-scheduled": "Board backup scheduled successfully", - "board-cleanup-failed": "Failed to schedule board cleanup", - "board-cleanup-scheduled": "Board cleanup scheduled successfully", - "board-operations": "Board Operations", - "cron-jobs": "Scheduled Jobs", - "cron-migrations": "Scheduled Migrations", - "cron-job-delete-confirm": "Are you sure you want to delete this scheduled job?", - "cron-job-delete-failed": "Failed to delete scheduled job", - "cron-job-deleted": "Scheduled job deleted successfully", - "cron-job-pause-failed": "Failed to pause scheduled job", - "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", - "filesystem-path-description": "Base path for file storage", - "gridfs-enabled": "GridFS Enabled", - "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", - "migration-pause-failed": "Failed to pause migrations", - "migration-paused": "Migrations paused successfully", - "migration-progress": "Migration Progress", - "migration-start-failed": "Failed to start migrations", - "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", - "migration-status": "Migration Status", - "migration-stop-confirm": "Are you sure you want to stop all migrations?", - "migration-stop-failed": "Failed to stop migrations", - "migration-stopped": "Migrations stopped successfully", - "mongodb-gridfs-storage": "MongoDB GridFS Storage", - "pause-all-migrations": "Pause All Migrations", - "s3-access-key": "S3 Access Key", - "s3-access-key-description": "AWS S3 access key for authentication", - "s3-access-key-placeholder": "Enter S3 access key", - "s3-bucket": "S3 Bucket", - "s3-bucket-description": "S3 bucket name for storing files", - "s3-connection-failed": "S3 connection failed", - "s3-connection-success": "S3 connection successful", - "s3-enabled": "S3 Enabled", - "s3-enabled-description": "Use AWS S3 or MinIO for file storage", - "s3-endpoint": "S3 Endpoint", - "s3-endpoint-description": "S3 endpoint URL (e.g., s3.amazonaws.com or minio.example.com)", - "s3-minio-storage": "S3/MinIO Storage", - "s3-port": "S3 Port", - "s3-port-description": "S3 endpoint port number", - "s3-region": "S3 Region", - "s3-region-description": "AWS S3 region (e.g., us-east-1)", - "s3-secret-key": "S3 Secret Key", - "s3-secret-key-description": "AWS S3 secret key for authentication", - "s3-secret-key-placeholder": "Enter S3 secret key", - "s3-secret-key-required": "S3 secret key is required", - "s3-settings-save-failed": "Failed to save S3 settings", - "s3-settings-saved": "S3 settings saved successfully", - "s3-ssl-enabled": "S3 SSL Enabled", - "s3-ssl-enabled-description": "Use SSL/TLS for S3 connections", - "save-s3-settings": "Save S3 Settings", - "schedule-board-archive": "Schedule Board Archive", - "schedule-board-backup": "Schedule Board Backup", - "schedule-board-cleanup": "Schedule Board Cleanup", - "scheduled-board-operations": "Scheduled Board Operations", - "start-all-migrations": "Start All Migrations", - "stop-all-migrations": "Stop All Migrations", - "test-s3-connection": "Test S3 Connection", - "writable-path": "Writable Path", - "writable-path-description": "Base directory path for file storage", - "add-job": "Add Job", - "attachment-migration": "Attachment Migration", - "attachment-monitoring": "Attachment Monitoring", - "attachment-settings": "Attachment Settings", - "attachment-storage-settings": "Storage Settings", - "automatic-migration": "Automatic Migration", - "back-to-settings": "Back to Settings", - "board-id": "Board ID", - "board-migration": "Board Migration", - "board-migrations": "Board Migrations", - "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", - "cleanup": "Cleanup", - "cleanup-old-jobs": "Cleanup Old Jobs", - "completed": "Completed", - "conversion-info-text": "This conversion is performed once per board and improves performance. You can continue using the board normally.", - "converting-board": "Converting Board", - "converting-board-description": "Converting board structure for improved functionality. This may take a few moments.", - "cpu-cores": "CPU Cores", - "cpu-usage": "CPU Usage", - "current-action": "Current Action", - "database-migration": "Database Migration", - "database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.", - "database-migrations": "Database Migrations", - "days-old": "Days Old", - "duration": "Duration", - "errors": "Errors", - "estimated-time-remaining": "Estimated time remaining", - "every-1-day": "Every 1 day", - "every-1-hour": "Every 1 hour", - "every-1-minute": "Every 1 minute", - "every-10-minutes": "Every 10 minutes", - "every-30-minutes": "Every 30 minutes", - "every-5-minutes": "Every 5 minutes", - "every-6-hours": "Every 6 hours", - "export-monitoring": "Export Monitoring", - "filesystem-attachments": "Filesystem Attachments", - "filesystem-size": "Filesystem Size", - "filesystem-storage": "Filesystem Storage", - "force-board-scan": "Force Board Scan", - "gridfs-attachments": "GridFS Attachments", - "gridfs-size": "GridFS Size", - "gridfs-storage": "GridFS", - "hide-list-on-minicard": "Hide List on Minicard", - "idle-migration": "Idle Migration", - "job-description": "Job Description", - "job-details": "Job Details", - "job-name": "Job Name", - "job-queue": "Job Queue", - "last-run": "Last Run", - "max-concurrent": "Max Concurrent", - "memory-usage": "Memory Usage", - "migrate-all-to-filesystem": "Migrate All to Filesystem", - "migrate-all-to-gridfs": "Migrate All to GridFS", - "migrate-all-to-s3": "Migrate All to S3", - "migrated-attachments": "Migrated Attachments", - "migration-batch-size": "Batch Size", - "migration-batch-size-description": "Number of attachments to process in each batch (1-100)", - "migration-cpu-threshold": "CPU Threshold (%)", - "migration-cpu-threshold-description": "Pause migration when CPU usage exceeds this percentage (10-90)", - "migration-delay-ms": "Delay (ms)", - "migration-delay-ms-description": "Delay between batches in milliseconds (100-10000)", - "migration-detector": "Migration Detector", - "migration-info-text": "Database migrations are performed once and improve system performance. The process continues in the background even if you close your browser.", - "migration-log": "Migration Log", - "migration-markers": "Migration Markers", - "migration-resume-failed": "Failed to resume migration", - "migration-resumed": "Migration resumed", - "migration-steps": "Migration Steps", - "migration-warning-text": "Please do not close your browser during migration. The process will continue in the background but may take longer to complete.", - "monitoring-export-failed": "Failed to export monitoring data", - "monitoring-refresh-failed": "Failed to refresh monitoring data", - "next": "Next", - "next-run": "Next Run", - "of": "of", - "operation-type": "Operation Type", - "overall-progress": "Overall Progress", - "page": "Page", - "pause-migration": "Pause Migration", - "previous": "Previous", - "refresh": "Refresh", - "refresh-monitoring": "Refresh Monitoring", - "remaining-attachments": "Remaining Attachments", - "resume-migration": "Resume Migration", - "run-once": "Run once", - "s3-attachments": "S3 Attachments", - "s3-size": "S3 Size", - "s3-storage": "S3", - "scanning-status": "Scanning Status", - "schedule": "Schedule", - "search-boards-or-operations": "Search boards or operations...", - "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", - "showing": "Showing", - "start-test-operation": "Start Test Operation", - "start-time": "Start Time", - "step-progress": "Step Progress", - "stop-migration": "Stop Migration", - "storage-distribution": "Storage Distribution", - "system-resources": "System Resources", - "total-attachments": "Total Attachments", - "total-operations": "Total Operations", - "total-size": "Total Size", - "unmigrated-boards": "Unmigrated Boards", - "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" -} diff --git a/imports/i18n/data/af.i18n.json b/imports/i18n/data/af.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/af.i18n.json +++ b/imports/i18n/data/af.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/af_ZA.i18n.json b/imports/i18n/data/af_ZA.i18n.json index 9657cf9ab..ecc4080e2 100644 --- a/imports/i18n/data/af_ZA.i18n.json +++ b/imports/i18n/data/af_ZA.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ar-DZ.i18n.json b/imports/i18n/data/ar-DZ.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/ar-DZ.i18n.json +++ b/imports/i18n/data/ar-DZ.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ar-EG.i18n.json b/imports/i18n/data/ar-EG.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/ar-EG.i18n.json +++ b/imports/i18n/data/ar-EG.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ar.i18n.json b/imports/i18n/data/ar.i18n.json index 8e85f885f..b4628a45d 100644 --- a/imports/i18n/data/ar.i18n.json +++ b/imports/i18n/data/ar.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "تعليق محذوف %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "نماذج", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "إضافة مرفق", @@ -98,7 +86,6 @@ "add-card": "إضافة بطاقة", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "إضافة قائمة", "setListWidthPopup-title": "تعيين العرض", "set-list-width": "تعيين العرض", "set-list-width-value": "تعيين الحد الأدنى والأقصى للعرض (بالبكسل)", @@ -122,10 +109,10 @@ "add-after-list": "أضف بعد القائمة", "add-members": "إضافة أعضاء", "added": "أُضيف", - "addMemberPopup-title": "إضافة أعضاء", + "addMemberPopup-title": "أعضاء", "memberPopup-title": "أفضليات الأعضاء", "admin": "المدير", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "إمكانية مشاهدة و تعديل و حذف أعضاء ، و تعديل إعدادات اللوحة أيضا.", "admin-announcement": "إعلان", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "عنوان URL لصورة الخلفية", "add-background-image": "إضافة صورة الخلفية", "remove-background-image": "إزالة صورة الخلفية", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "جميع إعدادات اللوحات", - "boardInfoOnMyBoardsPopup-title": "جميع إعدادات اللوحات", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "جميع إعدادات اللوحات", + "boardInfoOnMyBoardsPopup-title" : "جميع إعدادات اللوحات", "boardInfoOnMyBoards-title": "جميع إعدادات اللوحات", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s نجوم", "board-not-found": "لوحة مفقودة", "board-private-info": "سوف تصبح هذه اللوحة <strong>خاصة</strong>", @@ -286,8 +269,6 @@ "change-permissions": "تعديل الصلاحيات", "change-settings": "تغيير الاعدادات", "changeAvatarPopup-title": "تعديل الصورة الشخصية", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "تغيير اللغة", "changePasswordPopup-title": "تغيير كلمة المرور", "changePermissionsPopup-title": "تعديل الصلاحيات", @@ -335,16 +316,10 @@ "comment-placeholder": "أكتب تعليق", "comment-only": "التعليق فقط", "comment-only-desc": "يمكن التعليق على بطاقات فقط.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "لا يوجد تعليقات", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "عامل", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "حاسوب", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "نسخ رابط البطاقة إلى الحافظة", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "ربط البطاقة", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "إنشاء", "createBoardPopup-title": "إنشاء لوحة", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "استيراد لوحة", "createLabelPopup-title": "إنشاء علامة", "createCustomField": "انشاء حقل", @@ -385,7 +358,7 @@ "date": "تاريخ", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "صورة شخصية افتراضية", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "القائمات", "swimlanes": "خطوط السباحة", - "calendar": "التقويم", - "gantt": "Gantt", "log-out": "تسجيل الخروج", "log-in": "تسجيل الدخول", "loginPopup-title": "تسجيل الدخول", "memberMenuPopup-title": "أفضليات الأعضاء", - "grey-icons": "Grey Icons", "members": "أعضاء", "menu": "القائمة", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "التحرك إلى القاع", "moveCardToTop-title": "التحرك إلى الأعلى", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "تحديد أكثر من واحدة", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "لا توجد نتائج", "normal": "عادي", "normal-desc": "يمكن مشاهدة و تعديل البطاقات. لا يمكن تغيير إعدادات الضبط.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "السماح بتغيير البريد الإلكتروني", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "حدد اللون", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "اختر لوناً", "setSwimlaneColorPopup-title": "اختر لوناً", "setListColorPopup-title": "اختر لوناً", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "شخص", "my-cards": "My Cards", "card": "بطاقة", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "إخفاء جميع عناصر قائمة التحقق", "support": "دعم", "supportPopup-title": "دعم", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "تم تمكين صفحة إمكانية الوصول", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "الوقت", - "cron-error-message": "Error Message", - "cron-error-details": "تفاصيل", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "بداية", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "تفاصيل", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "تأكيد", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ary.i18n.json b/imports/i18n/data/ary.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/ary.i18n.json +++ b/imports/i18n/data/ary.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ast-ES.i18n.json b/imports/i18n/data/ast-ES.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/ast-ES.i18n.json +++ b/imports/i18n/data/ast-ES.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/az-AZ.i18n.json b/imports/i18n/data/az-AZ.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/az-AZ.i18n.json +++ b/imports/i18n/data/az-AZ.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/az-LA.i18n.json b/imports/i18n/data/az-LA.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/az-LA.i18n.json +++ b/imports/i18n/data/az-LA.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/az.i18n.json b/imports/i18n/data/az.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/az.i18n.json +++ b/imports/i18n/data/az.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/bg.i18n.json b/imports/i18n/data/bg.i18n.json index bdbcf6c48..043015731 100644 --- a/imports/i18n/data/bg.i18n.json +++ b/imports/i18n/data/bg.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "изтрит коментар %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Шаблони", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Добави прикачен файл", @@ -98,7 +86,6 @@ "add-card": "Добави карта", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Добави списък", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Добави членове", "added": "Добавено", - "addMemberPopup-title": "Добави членове", + "addMemberPopup-title": "Членове", "memberPopup-title": "Настройки на профила", "admin": "Администратор", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Съобщение", "admin-announcement-active": "Активно системно обявление", "admin-announcement-title": "Съобщение от администратора", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s звезди", "board-not-found": "Таблото не е намерено", "board-private-info": "Това табло ще бъде <strong>лично</strong>", @@ -286,8 +269,6 @@ "change-permissions": "Промени правата", "change-settings": "Промяна на настройките", "changeAvatarPopup-title": "Промени аватара", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Промени езика", "changePasswordPopup-title": "Промени паролата", "changePermissionsPopup-title": "Промени правата", @@ -335,16 +316,10 @@ "comment-placeholder": "Напиши коментар", "comment-only": "Само коментар", "comment-only-desc": "Може да коментира само в карти.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "Няма коментари", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Не може да вижда коментари и активност", "worker": "Работник", "worker-desc": "Може само да премества карти, да ги добавя към себе си и да коментира", "computer": "Компютър", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Копирай връзката на картата в клипборда", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Свържи картата", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Създай", "createBoardPopup-title": "Създай Табло", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Импортирай Табло", "createLabelPopup-title": "Създай Табло", "createCustomField": "Създай Поле", @@ -385,7 +358,7 @@ "date": "Дата", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Отказ", "default-avatar": "Основен аватар", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Можете да преместите списъка в Архива, за да го премахнете от Таблото и така да запазите активността в него.", "lists": "Списъци", "swimlanes": "Коридори", - "calendar": "Календар", - "gantt": "План", "log-out": "Изход", "log-in": "Вход", "loginPopup-title": "Вход", "memberMenuPopup-title": "Настройки на профила", - "grey-icons": "Grey Icons", "members": "Членове", "menu": "Меню", "move-selection": "Премести избраното", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Премести в края", "moveCardToTop-title": "Премести в началото", "moveSelectionPopup-title": "Премести избраното", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Множествен избор", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "Няма резултати", "normal": "Нормално", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Получавате информация за всички табла, списъци и карти, които наблюдавате", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Разреши промяна на имейла", "accounts-allowUserNameChange": "Позволи смяна на потребителско име", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Създаден на", "modifiedAt": "Modified at", "verified": "Потвърден", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Промени датата на получаване", "editCardEndDatePopup-title": "Промени датата на завършване", "setCardColorPopup-title": "Задай цвят", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Избери цвят", "setSwimlaneColorPopup-title": "Избери цвят", "setListColorPopup-title": "Избери цвят", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Всички списъци, карти, имена и действия ще бъдат изтрити и няма да можете да възстановите съдържанието на дъската. Няма връщане назад.", "boardDeletePopup-title": "Изтриване на Таблото?", "delete-board": "Изтрий таблото", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Подзадачи за табло __board__", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Изглед", "hide-logo": "Скрий логото", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Карта", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Списък", "board": "Табло", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Време", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Начало", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Състояние", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Завършено", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/br.i18n.json b/imports/i18n/data/br.i18n.json index e78c2bf6a..899f9dbb2 100644 --- a/imports/i18n/data/br.i18n.json +++ b/imports/i18n/data/br.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Ouzhpenn izili", "added": "Ouzhpennet", - "addMemberPopup-title": "Ouzhpenn izili", + "addMemberPopup-title": "Izili", "memberPopup-title": "Member Settings", "admin": "Merour", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stered", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Kemmañ ger-tremen", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Krouiñ", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Izili", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ca.i18n.json b/imports/i18n/data/ca.i18n.json index bb1451792..019a5eacd 100644 --- a/imports/i18n/data/ca.i18n.json +++ b/imports/i18n/data/ca.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "Ha esborrar el comentari %s", "activity-receivedDate": "editat la data de recepció a %s de %s", "activity-startDate": "data d'inici editada a %s de %s", - "allboards.starred": "Starred", - "allboards.templates": "Plantilles", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "data de venciment editada a %s de %s", "activity-endDate": "data de finalització editada a %s de %s", "add-attachment": "Afegeix adjunt", @@ -98,7 +86,6 @@ "add-card": "Afegeix Fitxa", "add-card-to-top-of-list": "Afegeix una fitxa al principi de la llista", "add-card-to-bottom-of-list": "Afegeix una fitxa al final de la llista", - "addListPopup-title": "Afegeix llista", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Membres d'Afegeix", "added": "Afegit", - "addMemberPopup-title": "Membres d'Afegeix", + "addMemberPopup-title": "Membres", "memberPopup-title": "Configura membres", "admin": "Administrador", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Pots veure i editar fitxes, eliminar usuaris, canviar la configuració del tauler.", "admin-announcement": "Alertes", "admin-announcement-active": "Activar alertes del Sistema", "admin-announcement-title": "Alertes d'administració", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Mostra tots els Taulers", - "board-info-on-my-boards": "Configuració de tots els taulers", - "boardInfoOnMyBoardsPopup-title": "Configuració de tots els taulers", + "show-at-all-boards-page" : "Mostra tots els Taulers", + "board-info-on-my-boards" : "Configuració de tots els taulers", + "boardInfoOnMyBoardsPopup-title" : "Configuració de tots els taulers", "boardInfoOnMyBoards-title": "Configuració de tots els taulers", "show-card-counter-per-list": "Mostra el recompte de fitxes per llista", "show-board_members-avatar": "Mostra els avatars dels membres del tauler", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s estrelles", "board-not-found": "No s'ha trobat el tauler", "board-private-info": "Aquest tauler serà <strong> privat.", @@ -286,8 +269,6 @@ "change-permissions": "Canvia permisos", "change-settings": "Canvia configuració", "changeAvatarPopup-title": "Avatar de Canvia", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Canvia idioma", "changePasswordPopup-title": "Canvia la contrasenya", "changePermissionsPopup-title": "Canvia permisos", @@ -335,16 +316,10 @@ "comment-placeholder": "Escriu un comentari", "comment-only": "Només comentaris", "comment-only-desc": "Només pots fer comentaris a les fitxes", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Està segur que vols esborrar el comentari?", "deleteCommentPopup-title": "Esborrar el comentari?", "no-comments": "Sentit comentaris", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "No es poden veure els comentaris i activitats.", "worker": "Treballador", "worker-desc": "Només pot moure cartes, assignar-se a la fitxa i comentar.", "computer": "Ordinador", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Esteu segur que voleu suprimir la llista de comprovació?", "subtaskDeletePopup-title": "Vols suprimir la subtasca?", "checklistDeletePopup-title": "Vols suprimir la llista de comprovació?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copia l'enllaç de la ftixa al porta-retalls", "copy-text-to-clipboard": "Copia el text al porta-retalls", "linkCardPopup-title": "Fitxa d'enllaç", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Títol de la primera fitxa\", \"description\":\"Descripció de la primera fitxa\"}, {\"title\":\"Títol de la segona fitxa\",\"description\":\"Descripció de la segona fitxa \"},{\"title\":\"Títol de l'última fitxa\",\"description\":\"Descripció de l'última fitxa\"} ]", "create": "Crea", "createBoardPopup-title": "Crea tauler", - "createTemplateContainerPopup-title": "Afegeix un Contenidor de plantilles", "chooseBoardSourcePopup-title": "Importa tauler", "createLabelPopup-title": "Crea etiqueta", "createCustomField": "Crear campament", @@ -385,7 +358,7 @@ "date": "Dades", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Declina", "default-avatar": "Avatar per defecte", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Podeu moure una llista a Arxivar per eliminar-la del tauler i preservar l'activitat.", "lists": "Llistes", "swimlanes": "Carrils", - "calendar": "Calendari", - "gantt": "Gantt", "log-out": "Finalitza la sessió", "log-in": "Inicia sessió", "loginPopup-title": "Inicia sessió", "memberMenuPopup-title": "Configura membres", - "grey-icons": "Grey Icons", "members": "Membres", "menu": "Menú", "move-selection": "Mou la selecció", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Mou a la part inferior", "moveCardToTop-title": "Mou a la part superior", "moveSelectionPopup-title": "Mou la selecció", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selecció", "multi-selection-label": "Estableix una etiqueta per a la selecció", "multi-selection-member": "Estableix un membre per a la selecció", @@ -587,8 +555,6 @@ "no-results": "Sense resultats", "normal": "Normal", "normal-desc": "Podeu veure i editar fitxes. No podeu canviar la configuració.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "La invitació no ha esta acceptada encara", "notify-participate": "Rep actualitzacions de les fitxes en què participis com a creador o membre", "notify-watch": "Rebre actualitzacions de qualsevol tauler, llista o fitxa en observació", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Permet modificar correu electrònic", "accounts-allowUserNameChange": "Permet el canvi de nom d'usuari", "tableVisibilityMode-allowPrivateOnly": "Visibilitat dels taulers: permet només taulers privats", - "tableVisibilityMode": "Visibilitat de taulers", + "tableVisibilityMode" : "Visibilitat de taulers", "createdAt": "Creat", "modifiedAt": "Modificat a", "verified": "Verificat", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Canvia la data de recepció", "editCardEndDatePopup-title": "Canvia la data de finalització", "setCardColorPopup-title": "Estableix el color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Trieu un color", "setSwimlaneColorPopup-title": "Trieu un color", "setListColorPopup-title": "Trieu un color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Se suprimiran totes les llistes, fitxes, etiquetes i activitats i no podreu recuperar el contingut del tauler. No hi ha cap desfer.", "boardDeletePopup-title": "Vols suprimir el tauler?", "delete-board": "Suprimeix el tauler", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasques per al tauler __board__", @@ -942,13 +905,6 @@ "authentication-method": "Mètode d'autenticació", "authentication-type": "Tipus d'autenticació", "custom-product-name": "Nom del producte personalitzat", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Disseny", "hide-logo": "Amaga el logotip", "hide-card-counter-list": "Amaga la llista de comptadors de fitxes a Tots els taulers", @@ -979,8 +935,6 @@ "a-endAt": "l'hora de finalització modificada", "a-startAt": "modificació de l'hora d'inici", "a-receivedAt": "temps rebut modificat per ser", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "s'acosta l'hora de venciment actual %s", "pastdue": "L'hora de venciment actual %s ha passat", "duenow": "L'hora de venciment actual %s és avui", @@ -989,7 +943,7 @@ "act-almostdue": "estava recordant que el venciment actual (__timeValue__) de la __card__ s'acosta", "act-pastdue": "estava recordant que el venciment actual (__timeValue__) de la __card__ ha passat", "act-duenow": "estava recordant el venciment actual (__timeValue__) de __card__ ara", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Has estat esmentat a [__board__] __list__/__card__", "delete-user-confirm-popup": "Esteu segur que voleu suprimir aquest compte? No hi ha cap desfer.", "delete-team-confirm-popup": "Esteu segur que voleu suprimir aquest equip? No hi ha cap desfer.", "delete-org-confirm-popup": "Esteu segur que voleu suprimir aquesta organització? No hi ha cap desfer.", @@ -1013,7 +967,6 @@ "view-all": "Veure tot", "filter-by-unread": "Filtra per No llegit", "mark-all-as-read": "Marcar tots com a llegits", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Elimina totes les llegits", "allow-rename": "Permet canviar el nom", "allowRenamePopup-title": "Permet canviar el nom", @@ -1048,10 +1001,6 @@ "person": "Persona", "my-cards": "Les meves fitxes", "card": "Fitxa", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Llista", "board": "Tauler", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Hora", - "cron-error-message": "Error Message", - "cron-error-details": "Detalls", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Comença", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Estat", - "migration-progress-details": "Detalls", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completat", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirmeu", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ca@valencia.i18n.json b/imports/i18n/data/ca@valencia.i18n.json index 246ef9915..ab9a378b1 100644 --- a/imports/i18n/data/ca@valencia.i18n.json +++ b/imports/i18n/data/ca@valencia.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ca_ES.i18n.json b/imports/i18n/data/ca_ES.i18n.json index 2efc6ddcd..7e2ba5ba7 100644 --- a/imports/i18n/data/ca_ES.i18n.json +++ b/imports/i18n/data/ca_ES.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/cmn.i18n.json b/imports/i18n/data/cmn.i18n.json index ddc39fddf..f99bd548e 100644 --- a/imports/i18n/data/cmn.i18n.json +++ b/imports/i18n/data/cmn.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/cs-CZ.i18n.json b/imports/i18n/data/cs-CZ.i18n.json index 3508e6da2..0e915cedf 100644 --- a/imports/i18n/data/cs-CZ.i18n.json +++ b/imports/i18n/data/cs-CZ.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "smazat komentář %s", "activity-receivedDate": "editoval(a) datum přijetí na %s z %s", "activity-startDate": "editoval(a) datum zahájení na %s z %s", - "allboards.starred": "Starred", - "allboards.templates": "Šablony", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "editoval(a) termín dokončení na %s z %s", "activity-endDate": "editoval(a) datum ukončení na %s z %s", "add-attachment": "Přidat přílohu", @@ -98,7 +86,6 @@ "add-card": "Přidat kartu", "add-card-to-top-of-list": "Přidat kartu na začátek seznamu", "add-card-to-bottom-of-list": "Přidat kartu na konec seznamu", - "addListPopup-title": "Přidat sloupec", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Přidat členy", "added": "Přidán", - "addMemberPopup-title": "Přidat členy", + "addMemberPopup-title": "Členové", "memberPopup-title": "Nastavení uživatele", "admin": "Administrátor", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Může zobrazovat a upravovat karty, mazat členy a měnit nastavení tabla.", "admin-announcement": "Oznámení", "admin-announcement-active": "Aktivní oznámení v celém systému", "admin-announcement-title": "Oznámení od administrátora", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s hvězdiček", "board-not-found": "Tablo nenalezeno", "board-private-info": "Toto tablo bude <strong>soukromé</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Změnit oprávnění", "change-settings": "Změnit nastavení", "changeAvatarPopup-title": "Změnit avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Změnit jazyk", "changePasswordPopup-title": "Změnit heslo", "changePermissionsPopup-title": "Změnit oprávnění", @@ -335,16 +316,10 @@ "comment-placeholder": "Text komentáře", "comment-only": "Pouze komentáře", "comment-only-desc": "Může přidávat komentáře pouze do karet.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "Žádné komentáře", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Nemůže vidět komentáře a aktivity", "worker": "Pracovník", "worker-desc": "Je možné pouze přesouvat karty, přiřazovat ke kartám a komentovat.", "computer": "Počítač", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Kopírovat adresu karty do mezipaměti", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Propojit kartu", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Nadpis první karty\", \"description\":\"Popis první karty\"}, {\"title\":\"Nadpis druhé karty\",\"description\":\"Popis druhé karty\"},{\"title\":\"Nadpis poslední kary\",\"description\":\"Popis poslední karty\"} ]", "create": "Vytvořit", "createBoardPopup-title": "Vytvořit tablo", - "createTemplateContainerPopup-title": "Přidat kontejner šablony", "chooseBoardSourcePopup-title": "Importovat tablo", "createLabelPopup-title": "Vytvořit štítek", "createCustomField": "Vytvořit pole", @@ -385,7 +358,7 @@ "date": "Datum", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Zamítnout", "default-avatar": "Výchozí avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Seznam můžete přesunout do archivu, abyste jej odstranili z tabla a zachovali si svou aktivitu.", "lists": "Sloupce", "swimlanes": "Swimlanes", - "calendar": "Kalendář", - "gantt": "Gannt", "log-out": "Odhlásit", "log-in": "Přihlásit", "loginPopup-title": "Přihlásit", "memberMenuPopup-title": "Nastavení uživatele", - "grey-icons": "Grey Icons", "members": "Členové", "menu": "Menu", "move-selection": "Přesunout výběr", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Přesunout dolu", "moveCardToTop-title": "Přesunout nahoru", "moveSelectionPopup-title": "Přesunout výběr", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-výběr", "multi-selection-label": "Vyberte štítek", "multi-selection-member": "Vyberte člena", @@ -587,8 +555,6 @@ "no-results": "Žádné výsledky", "normal": "Normální", "normal-desc": "Může zobrazovat a upravovat karty. Nemůže měnit nastavení.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Pozvánka ještě nebyla přijmuta", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Dostane aktualitace to všech tabel, sloupců nebo karet, které sledujete", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Povolit změnu Emailu", "accounts-allowUserNameChange": "Povolit změnu uživatelského jména", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Vytvořeno v", "modifiedAt": "Modifikováno", "verified": "Ověřen", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Změnit datum přijetí", "editCardEndDatePopup-title": "Změnit datum konce", "setCardColorPopup-title": "Nastav barvu", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Vyber barvu", "setSwimlaneColorPopup-title": "Vyber barvu", "setListColorPopup-title": "Vyber barvu", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Všechny sloupce, štítky a aktivity budou smazány a obsah tabla nebude možné obnovit. Toto nelze vrátit zpět.", "boardDeletePopup-title": "Smazat tablo?", "delete-board": "Smazat tablo", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Podúkoly pro tablo __board__", @@ -942,13 +905,6 @@ "authentication-method": "Metoda autentizace", "authentication-type": "Typ autentizace", "custom-product-name": "Vlastní název produktu", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Uspořádání", "hide-logo": "Skrýt logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "změnil(a) čas ukončení", "a-startAt": "změnil(a) čas zahájení", "a-receivedAt": "změnil(a) čas přijetí", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "Stávající termín dokončení %s se blíží", "pastdue": "Stávající termín dokončení %s je v minulosti", "duenow": "Stávající termín dokončení %s je dnes", @@ -989,7 +943,7 @@ "act-almostdue": "připomínal(a) , že stávající termín dokončení (__timeValue__) __card__ se blíží", "act-pastdue": "připomínal(a), že stávající termín dokončení (__timeValue__) __card__ byl v minulosti", "act-duenow": "připomínal(a), že stávající termín dokončení (__timeValue__) __card__ je teď", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Byli jste zmíněni v [__board__] __list__/__card__", "delete-user-confirm-popup": "Jste si jisti, že chcete smazat tento účet? Tuto akci nelze vrátit zpět.", "delete-team-confirm-popup": "Jste si jisti, že chcete smazat tento tým? Tuto akci nelze vrátit zpět.", "delete-org-confirm-popup": "Jste si jisti, že chcete smazat tuto organizaci? Tuto akci nelze vrátit zpět.", @@ -1013,7 +967,6 @@ "view-all": "Zobrazit vše", "filter-by-unread": "Zobrazit nepřečtené", "mark-all-as-read": "Označit vše jako přečtené", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Odstranit přečtené", "allow-rename": "Povolit přejmenování", "allowRenamePopup-title": "Povolit přejmenování", @@ -1048,10 +1001,6 @@ "person": "Osoba", "my-cards": "Moje karty", "card": "Karta", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Seznam", "board": "Tablo", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Čas", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Stav", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Dokončeno", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/cs.i18n.json b/imports/i18n/data/cs.i18n.json index 1a244d066..58abbc60d 100644 --- a/imports/i18n/data/cs.i18n.json +++ b/imports/i18n/data/cs.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "smazat komentář %s", "activity-receivedDate": "editoval(a) datum přijetí na %s z %s", "activity-startDate": "editoval(a) datum zahájení na %s z %s", - "allboards.starred": "Starred", - "allboards.templates": "Šablony", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "editoval(a) termín dokončení na %s z %s", "activity-endDate": "editoval(a) datum ukončení na %s z %s", "add-attachment": "Přidat přílohu", @@ -98,7 +86,6 @@ "add-card": "Přidat kartu", "add-card-to-top-of-list": "Přidat kartu na začátek seznamu", "add-card-to-bottom-of-list": "Přidat kartu na konec seznamu", - "addListPopup-title": "Přidat sloupec", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Přidat za seznam", "add-members": "Přidat členy", "added": "Přidán", - "addMemberPopup-title": "Přidat členy", + "addMemberPopup-title": "Členové", "memberPopup-title": "Nastavení uživatele", "admin": "Administrátor", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Může zobrazovat a upravovat karty, mazat členy a měnit nastavení tabla.", "admin-announcement": "Oznámení", "admin-announcement-active": "Aktivní oznámení v celém systému", "admin-announcement-title": "Oznámení od administrátora", @@ -167,16 +154,12 @@ "board-background-image-url": "URL obrázku na pozadí", "add-background-image": "Přidat obrázek na pozadí", "remove-background-image": "Odstranit obrázek z pozadí", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s hvězdiček", "board-not-found": "Tablo nenalezeno", "board-private-info": "Toto tablo bude <strong>soukromé</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Změnit oprávnění", "change-settings": "Změnit nastavení", "changeAvatarPopup-title": "Změnit avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Změnit jazyk", "changePasswordPopup-title": "Změnit heslo", "changePermissionsPopup-title": "Změnit oprávnění", @@ -335,16 +316,10 @@ "comment-placeholder": "Text komentáře", "comment-only": "Pouze komentáře", "comment-only-desc": "Může přidávat komentáře pouze do karet.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "Žádné komentáře", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Nemůže vidět komentáře a aktivity", "worker": "Pracovník", "worker-desc": "Je možné pouze přesouvat karty, přiřazovat ke kartám a komentovat.", "computer": "Počítač", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Kopírovat adresu karty do mezipaměti", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Propojit kartu", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Nadpis první karty\", \"description\":\"Popis první karty\"}, {\"title\":\"Nadpis druhé karty\",\"description\":\"Popis druhé karty\"},{\"title\":\"Nadpis poslední kary\",\"description\":\"Popis poslední karty\"} ]", "create": "Vytvořit", "createBoardPopup-title": "Vytvořit tablo", - "createTemplateContainerPopup-title": "Přidat kontejner šablony", "chooseBoardSourcePopup-title": "Importovat tablo", "createLabelPopup-title": "Vytvořit štítek", "createCustomField": "Vytvořit pole", @@ -385,7 +358,7 @@ "date": "Datum", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Zamítnout", "default-avatar": "Výchozí avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Seznam můžete přesunout do archivu, abyste jej odstranili z tabla a zachovali si svou aktivitu.", "lists": "Sloupce", "swimlanes": "Swimlanes", - "calendar": "Kalendář", - "gantt": "Gannt", "log-out": "Odhlásit", "log-in": "Přihlásit", "loginPopup-title": "Přihlásit", "memberMenuPopup-title": "Nastavení uživatele", - "grey-icons": "Grey Icons", "members": "Členové", "menu": "Menu", "move-selection": "Přesunout výběr", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Přesunout dolu", "moveCardToTop-title": "Přesunout nahoru", "moveSelectionPopup-title": "Přesunout výběr", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-výběr", "multi-selection-label": "Vyberte štítek", "multi-selection-member": "Vyberte člena", @@ -587,8 +555,6 @@ "no-results": "Žádné výsledky", "normal": "Normální", "normal-desc": "Může zobrazovat a upravovat karty. Nemůže měnit nastavení.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Pozvánka ještě nebyla přijmuta", "notify-participate": "Dostanete aktualizace o všech kartách, kterých se účastníte jako tvůrce nebo člen", "notify-watch": "Dostane aktualizace o všech tabel, sloupců nebo karet, které sledujete", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Povolit změnu Emailu", "accounts-allowUserNameChange": "Povolit změnu uživatelského jména", "tableVisibilityMode-allowPrivateOnly": "Viditelnost tabel: Povolit pouze soukromé tabla", - "tableVisibilityMode": "Viditelnost tabel", + "tableVisibilityMode" : "Viditelnost tabel", "createdAt": "Vytvořeno v", "modifiedAt": "Modifikováno", "verified": "Ověřen", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Změnit datum přijetí", "editCardEndDatePopup-title": "Změnit datum konce", "setCardColorPopup-title": "Nastav barvu", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Vyber barvu", "setSwimlaneColorPopup-title": "Vyber barvu", "setListColorPopup-title": "Vyber barvu", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Všechny sloupce, štítky a aktivity budou smazány a obsah tabla nebude možné obnovit. Toto nelze vrátit zpět.", "boardDeletePopup-title": "Smazat tablo?", "delete-board": "Smazat tablo", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Podúkoly pro tablo __board__", @@ -942,13 +905,6 @@ "authentication-method": "Metoda autentizace", "authentication-type": "Typ autentizace", "custom-product-name": "Vlastní název produktu", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Uspořádání", "hide-logo": "Skrýt logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "změnil(a) čas ukončení", "a-startAt": "změnil(a) čas zahájení", "a-receivedAt": "změnil(a) čas přijetí", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "Stávající termín dokončení %s se blíží", "pastdue": "Stávající termín dokončení %s je v minulosti", "duenow": "Stávající termín dokončení %s je dnes", @@ -989,7 +943,7 @@ "act-almostdue": "připomínal(a) , že stávající termín dokončení (__timeValue__) __card__ se blíží", "act-pastdue": "připomínal(a), že stávající termín dokončení (__timeValue__) __card__ byl v minulosti", "act-duenow": "připomínal(a), že stávající termín dokončení (__timeValue__) __card__ je teď", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Byli jste zmíněni v [__board__] __list__/__card__", "delete-user-confirm-popup": "Jste si jisti, že chcete smazat tento účet? Tuto akci nelze vrátit zpět.", "delete-team-confirm-popup": "Jste si jisti, že chcete smazat tento tým? Tuto akci nelze vrátit zpět.", "delete-org-confirm-popup": "Jste si jisti, že chcete smazat tuto organizaci? Tuto akci nelze vrátit zpět.", @@ -1013,7 +967,6 @@ "view-all": "Zobrazit vše", "filter-by-unread": "Zobrazit nepřečtené", "mark-all-as-read": "Označit vše jako přečtené", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Odstranit přečtené", "allow-rename": "Povolit přejmenování", "allowRenamePopup-title": "Povolit přejmenování", @@ -1048,10 +1001,6 @@ "person": "Osoba", "my-cards": "Moje karty", "card": "Karta", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Seznam", "board": "Tablo", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Čas", - "cron-error-message": "Error Message", - "cron-error-details": "Podrobnosti", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Stav", - "migration-progress-details": "Podrobnosti", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Dokončeno", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Potvrdit", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/cy-GB.i18n.json b/imports/i18n/data/cy-GB.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/cy-GB.i18n.json +++ b/imports/i18n/data/cy-GB.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/cy.i18n.json b/imports/i18n/data/cy.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/cy.i18n.json +++ b/imports/i18n/data/cy.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/da.i18n.json b/imports/i18n/data/da.i18n.json index a6952779b..bfe07883d 100644 --- a/imports/i18n/data/da.i18n.json +++ b/imports/i18n/data/da.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "slettede kommentar %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Skabeloner", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Tilføj vedhæftning", @@ -98,7 +86,6 @@ "add-card": "Tilføj kort", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Tilføj liste", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Tilføj efter liste", "add-members": "Tilføj medlemmer", "added": "Tilføjet", - "addMemberPopup-title": "Tilføj medlemmer", + "addMemberPopup-title": "Medlemmer", "memberPopup-title": "Medlemsindstillinger", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Kan se og redigere kort, fjerne medlemmer og ændre indstillinger for tavlen.", "admin-announcement": "Annoncering", "admin-announcement-active": "Aktivér annoncering på tværs af systemet", "admin-announcement-title": "Annoncering fra administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stjerner", "board-not-found": "Fandt ikke tavle", "board-private-info": "Denne tavle vil være <strong>privat</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Tilpas tilladelser", "change-settings": "Tilpas indstillinger", "changeAvatarPopup-title": "Tilpas avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Skift sprog", "changePasswordPopup-title": "Skift kodeord", "changePermissionsPopup-title": "Tilpas tilladelser", @@ -335,16 +316,10 @@ "comment-placeholder": "Skriv kommentar", "comment-only": "Kun kommentarer", "comment-only-desc": "Kan kun kommentere på kort.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "Ingen kommentarer", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Kan ikke se kommentarer og aktiviteter.", "worker": "Arbejder", "worker-desc": "Kan kun flytte kort, tildele sig selv til kort og kommentere.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Kopiér link til kort til udklipsholder", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Sammenkæd kort", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Opret", "createBoardPopup-title": "Opret tavle", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Importér tavle", "createLabelPopup-title": "Opret etikette", "createCustomField": "Opret felt", @@ -385,7 +358,7 @@ "date": "Dato", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Afslå", "default-avatar": "Standard-avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Du kan flytte en liste til arkivet for at fjerne det fra tavlen og bevare dets aktivitet.", "lists": "Lister", "swimlanes": "Svømmebaner", - "calendar": "Kalender", - "gantt": "Gantt", "log-out": "Log ud", "log-in": "Log ind", "loginPopup-title": "Log ind", "memberMenuPopup-title": "Medlemsindstillinger", - "grey-icons": "Grey Icons", "members": "Medlemmer", "menu": "Menu", "move-selection": "Flyt valgte", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Flyt til bunden", "moveCardToTop-title": "Flyt til toppen", "moveSelectionPopup-title": "Flyt valgte", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multivalg", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "Ingen resultater", "normal": "Normal", "normal-desc": "Du kan se og redigere kort. Indstillinger kan ikke ændres.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation er endnu ikke accepteret", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Modtag opdateringer for alle tavler eller kort som du følger", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Tillad ændring af e-mail", "accounts-allowUserNameChange": "Tillad ændring af brugernavn", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Oprettet per", "modifiedAt": "Modified at", "verified": "Verificeret", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Tilpas modtagelsesdato", "editCardEndDatePopup-title": "Tilpas slutdato", "setCardColorPopup-title": "Angiv farve", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Vælg en farve", "setSwimlaneColorPopup-title": "Vælg en farve", "setListColorPopup-title": "Vælg en farve", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Alle lister, kort, etiketter og aktiviteter vil blive slettet og du får ikke mulighed for at genskabe tavlens indhold. Dette kan ikke fortrydes.", "boardDeletePopup-title": "Slet tavle?", "delete-board": "Slet tavle", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Delopgaver for tavlen __board__", @@ -942,13 +905,6 @@ "authentication-method": "Godkendelsesmetode", "authentication-type": "Godkendelsestype", "custom-product-name": "Tilpasset produktnavn", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Skjul logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "ændrede sluttidspunkt til at være", "a-startAt": "ændrede starttidspunkt til at være", "a-receivedAt": "ændrede modtagelsestidspunkt til at være", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "aktuelt forfaldstidspunkt %s nærmer sig", "pastdue": "aktuelt forfaldstidspunkt %s er passeret", "duenow": "aktuelt forfaldstidspunkt %s er i dag", @@ -989,7 +943,7 @@ "act-almostdue": "påmindede om at aktuelt forfald (__timeValue__) for __card__ nærmer sig", "act-pastdue": "påmindede om at aktuelt forfald (__timeValue__) of __card__ er passeret", "act-duenow": "påmindede om at aktuelt forfald (__timeValue__) of __card__ er nu", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Du blev nævnt i [__board__] __list__/__card__", "delete-user-confirm-popup": "Er du sikker på du vil slette denne konto? Det er ikke muligt at fortryde.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "Vis alle", "filter-by-unread": "Filtrér efter ulæst", "mark-all-as-read": "Markér alle som læst", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Fjern alle læste", "allow-rename": "Tillad omdøb", "allowRenamePopup-title": "Tillad omdøb", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Kort", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Tid", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Fuldført", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/de-AT.i18n.json b/imports/i18n/data/de-AT.i18n.json index a591a0713..625ad5655 100644 --- a/imports/i18n/data/de-AT.i18n.json +++ b/imports/i18n/data/de-AT.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "löschte Kommentar %s", "activity-receivedDate": "hat Empfangsdatum zu %s geändert auf %s", "activity-startDate": "hat Startdatum zu %s geändert auf %s", - "allboards.starred": "Starred", - "allboards.templates": "Vorlagen", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "hat Fälligkeitsdatum zu %s geändert auf %s", "activity-endDate": "hat Enddatum zu %s geändert auf %s", "add-attachment": "Datei anhängen", @@ -98,7 +86,6 @@ "add-card": "Karte hinzufügen", "add-card-to-top-of-list": "Karte am Anfang der Liste hinzufügen", "add-card-to-bottom-of-list": "Karte am Ende der Liste hinzufügen", - "addListPopup-title": "Liste hinzufügen", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Mitglieder hinzufügen", "added": "Hinzugefügt", - "addMemberPopup-title": "Mitglieder hinzufügen", + "addMemberPopup-title": "Mitglieder", "memberPopup-title": "Nutzereinstellungen", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Kann Karten anzeigen und bearbeiten, Mitglieder entfernen und Boardeinstellungen ändern.", "admin-announcement": "Ankündigung", "admin-announcement-active": "Aktive systemweite Ankündigungen", "admin-announcement-title": "Ankündigung des Administrators", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s Sterne", "board-not-found": "Board nicht gefunden", "board-private-info": "Dieses Board wird <strong>privat</strong> sein.", @@ -286,8 +269,6 @@ "change-permissions": "Berechtigungen ändern", "change-settings": "Einstellungen ändern", "changeAvatarPopup-title": "Profilbild ändern", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Sprache ändern", "changePasswordPopup-title": "Passwort ändern", "changePermissionsPopup-title": "Berechtigungen ändern", @@ -335,16 +316,10 @@ "comment-placeholder": "Kommentar schreiben", "comment-only": "Nur Kommentare", "comment-only-desc": "Kann Karten nur kommentieren.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Sind Sie sicher, dass Sie den Kommentar löschen wollen?", "deleteCommentPopup-title": "Kommentar löschen?", "no-comments": "Keine Kommentare", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Kann keine Kommentare und Aktivitäten sehen.", "worker": "Arbeiter", "worker-desc": "Kann Karten nur verschieben, sich selbst zuweisen und kommentieren.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Wollen Sie diese Checkliste wirklich löschen?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Checkliste löschen?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Kopiere Link zur Karte in die Zwischenablage", "copy-text-to-clipboard": "Text in die Zwischenablage kopieren", "linkCardPopup-title": "Karte verknüpfen", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Titel der ersten Karte\", \"description\":\"Beschreibung der ersten Karte\"}, {\"title\":\"Titel der zweiten Karte\",\"description\":\"Beschreibung der zweiten Karte\"},{\"title\":\"Titel der letzten Karte\",\"description\":\"Beschreibung der letzten Karte\"} ]", "create": "Erstellen", "createBoardPopup-title": "Board erstellen", - "createTemplateContainerPopup-title": "Vorlagen-Container hinzufügen", "chooseBoardSourcePopup-title": "Board importieren", "createLabelPopup-title": "Label erstellen", "createCustomField": "Feld erstellen", @@ -385,7 +358,7 @@ "date": "Datum", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Ablehnen", "default-avatar": "Standard Profilbild", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Listen können ins Archiv verschoben werden, um sie aus dem Board zu entfernen und die Aktivitäten zu behalten.", "lists": "Listen", "swimlanes": "Swimlanes", - "calendar": "Kalender", - "gantt": "Gantt", "log-out": "Ausloggen", "log-in": "Einloggen", "loginPopup-title": "Einloggen", "memberMenuPopup-title": "Nutzereinstellungen", - "grey-icons": "Grey Icons", "members": "Mitglieder", "menu": "Menü", "move-selection": "Auswahl verschieben", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Ans Ende verschieben", "moveCardToTop-title": "Zum Anfang verschieben", "moveSelectionPopup-title": "Auswahl verschieben", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Mehrfachauswahl", "multi-selection-label": "Label für die Auswahl setzen", "multi-selection-member": "Mitglied für die Auswahl setzen", @@ -587,8 +555,6 @@ "no-results": "Keine Ergebnisse", "normal": "Normal", "normal-desc": "Kann Karten anzeigen und bearbeiten, aber keine Einstellungen ändern.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Die Einladung wurde noch nicht angenommen", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Benachrichtigungen über alle Boards, Listen oder Karten erhalten, die Sie beobachten", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Ändern der E-Mailadresse erlauben", "accounts-allowUserNameChange": "Ändern des Benutzernamens erlauben", "tableVisibilityMode-allowPrivateOnly": "Board-Sichtbarkeit: Erlaube ausschließlich private Boards", - "tableVisibilityMode": "Sichtbarkeit der Boards", + "tableVisibilityMode" : "Sichtbarkeit der Boards", "createdAt": "Erstellt am", "modifiedAt": "Geändert am", "verified": "Geprüft", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Empfangsdatum ändern", "editCardEndDatePopup-title": "Enddatum ändern", "setCardColorPopup-title": "Farbe festlegen", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Farbe wählen", "setSwimlaneColorPopup-title": "Farbe wählen", "setListColorPopup-title": "Farbe wählen", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Alle Listen, Karten, Labels und Akivitäten werden gelöscht und Sie können die Inhalte des Boards nicht wiederherstellen! Die Aktion kann nicht rückgängig gemacht werden.", "boardDeletePopup-title": "Board löschen?", "delete-board": "Board löschen", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Teilaufgabe für __board__ Board", @@ -942,13 +905,6 @@ "authentication-method": "Authentifizierungsmethode", "authentication-type": "Authentifizierungstyp", "custom-product-name": "Benutzerdefinierter Produktname", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Verstecke Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "hat Ende geändert auf", "a-startAt": "hat Startzeit geändert auf", "a-receivedAt": "hat Empfangszeit geändert auf", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "aktuelles Fälligkeitsdatum %s bevorstehend", "pastdue": "aktuelles Fälligkeitsdatum %s überschritten", "duenow": "aktuelles Fälligkeitsdatum %s heute", @@ -989,7 +943,7 @@ "act-almostdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist bevorstehend", "act-pastdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist vorbei", "act-duenow": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist jetzt", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Sie wurden in [__board__] __list__/__card__ erwähnt", "delete-user-confirm-popup": "Sind Sie sicher, dass Sie diesen Account löschen wollen? Die Aktion kann nicht rückgängig gemacht werden.", "delete-team-confirm-popup": "Sind Sie sicher, daß Sie dieses Team löschen wollen? Es gibt keine Möglichkeit, das rückgängig zu machen.", "delete-org-confirm-popup": "Sind Sie sicher, daß Sie diese Organisation löschen wollen? Es gibt keine Möglichkeit, das rückgängig zu machen.", @@ -1013,7 +967,6 @@ "view-all": "Alle anzeigen", "filter-by-unread": "Nur ungelesene", "mark-all-as-read": "Alle als gelesen markieren", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Alle gelesenen entfernen", "allow-rename": "Umbenennen erlauben", "allowRenamePopup-title": "Umbenennen erlauben", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "Meine Karten", "card": "Karte", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Liste", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Zeit", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "abgeschlossen", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Bestätigen", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/de-CH.i18n.json b/imports/i18n/data/de-CH.i18n.json index 48e695733..4c9682382 100644 --- a/imports/i18n/data/de-CH.i18n.json +++ b/imports/i18n/data/de-CH.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "löschte Kommentar %s", "activity-receivedDate": "hat Empfangsdatum zu %s geändert auf %s", "activity-startDate": "hat Startdatum zu %s geändert auf %s", - "allboards.starred": "Starred", - "allboards.templates": "Vorlagen", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "hat Fälligkeitsdatum zu %s geändert auf %s", "activity-endDate": "hat Enddatum zu %s geändert auf %s", "add-attachment": "Datei anhängen", @@ -98,7 +86,6 @@ "add-card": "Karte hinzufügen", "add-card-to-top-of-list": "Karte am Anfang der Liste hinzufügen", "add-card-to-bottom-of-list": "Karte am Ende der Liste hinzufügen", - "addListPopup-title": "Liste hinzufügen", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Mitglieder hinzufügen", "added": "Hinzugefügt", - "addMemberPopup-title": "Mitglieder hinzufügen", + "addMemberPopup-title": "Mitglieder", "memberPopup-title": "Nutzereinstellungen", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Kann Karten anzeigen und bearbeiten, Mitglieder entfernen und Boardeinstellungen ändern.", "admin-announcement": "Ankündigung", "admin-announcement-active": "Aktive systemweite Ankündigungen", "admin-announcement-title": "Ankündigung des Administrators", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Zeigen auf der Seite \"Alle Boards\"", - "board-info-on-my-boards": "Einstellungen für alle Boards", - "boardInfoOnMyBoardsPopup-title": "Einstellungen für alle Boards", + "show-at-all-boards-page" : "Zeigen auf der Seite \"Alle Boards\"", + "board-info-on-my-boards" : "Einstellungen für alle Boards", + "boardInfoOnMyBoardsPopup-title" : "Einstellungen für alle Boards", "boardInfoOnMyBoards-title": "Einstellungen für alle Boards", "show-card-counter-per-list": "Zeige Kartenanzahl pro Liste", "show-board_members-avatar": "Zeige Profilbilder der Board-Mitglieder", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s Sterne", "board-not-found": "Board nicht gefunden", "board-private-info": "Dieses Board wird <strong>privat</strong> sein.", @@ -286,8 +269,6 @@ "change-permissions": "Berechtigungen ändern", "change-settings": "Einstellungen ändern", "changeAvatarPopup-title": "Profilbild ändern", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Sprache ändern", "changePasswordPopup-title": "Passwort ändern", "changePermissionsPopup-title": "Berechtigungen ändern", @@ -335,16 +316,10 @@ "comment-placeholder": "Kommentar schreiben", "comment-only": "Nur Kommentare", "comment-only-desc": "Kann Karten nur kommentieren.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Sind Sie sicher, dass Sie den Kommentar löschen möchten?", "deleteCommentPopup-title": "Kommentar löschen?", "no-comments": "Keine Kommentare", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Kann keine Kommentare und Aktivitäten sehen.", "worker": "Arbeiter", "worker-desc": "Kann Karten nur verschieben, sich selbst zuweisen und kommentieren.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Wollen Sie diese Checkliste wirklich löschen?", "subtaskDeletePopup-title": "Teilaufgabe löschen?", "checklistDeletePopup-title": "Checkliste löschen?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Kopiere Link zur Karte in die Zwischenablage", "copy-text-to-clipboard": "Text in die Zwischenablage kopieren", "linkCardPopup-title": "Karte verknüpfen", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Titel der ersten Karte\", \"description\":\"Beschreibung der ersten Karte\"}, {\"title\":\"Titel der zweiten Karte\",\"description\":\"Beschreibung der zweiten Karte\"},{\"title\":\"Titel der letzten Karte\",\"description\":\"Beschreibung der letzten Karte\"} ]", "create": "Erstellen", "createBoardPopup-title": "Board erstellen", - "createTemplateContainerPopup-title": "Vorlagen-Container hinzufügen", "chooseBoardSourcePopup-title": "Board importieren", "createLabelPopup-title": "Label erstellen", "createCustomField": "Feld erstellen", @@ -385,7 +358,7 @@ "date": "Datum", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Ablehnen", "default-avatar": "Standard Profilbild", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Listen können ins Archiv verschoben werden, um sie aus dem Board zu entfernen und die Aktivitäten zu behalten.", "lists": "Listen", "swimlanes": "Swimlanes", - "calendar": "Kalender", - "gantt": "Gantt", "log-out": "Ausloggen", "log-in": "Einloggen", "loginPopup-title": "Einloggen", "memberMenuPopup-title": "Nutzereinstellungen", - "grey-icons": "Grey Icons", "members": "Mitglieder", "menu": "Menü", "move-selection": "Auswahl verschieben", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Ans Ende verschieben", "moveCardToTop-title": "Zum Anfang verschieben", "moveSelectionPopup-title": "Auswahl verschieben", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Mehrfachauswahl", "multi-selection-label": "Label für die Auswahl setzen", "multi-selection-member": "Mitglied für die Auswahl setzen", @@ -587,8 +555,6 @@ "no-results": "Keine Ergebnisse", "normal": "Normal", "normal-desc": "Kann Karten anzeigen und bearbeiten, aber keine Einstellungen ändern.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Die Einladung wurde noch nicht angenommen", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Benachrichtigungen über alle Boards, Listen oder Karten erhalten, die Sie beobachten", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Ändern der E-Mailadresse erlauben", "accounts-allowUserNameChange": "Ändern des Benutzernamens erlauben", "tableVisibilityMode-allowPrivateOnly": "Sichtbarkeit von Boards: Erlaube nur private Boards", - "tableVisibilityMode": "Sichtbarkeit von Boards", + "tableVisibilityMode" : "Sichtbarkeit von Boards", "createdAt": "Erstellt am", "modifiedAt": "Geändert am", "verified": "Geprüft", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Empfangsdatum ändern", "editCardEndDatePopup-title": "Enddatum ändern", "setCardColorPopup-title": "Farbe festlegen", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Farbe wählen", "setSwimlaneColorPopup-title": "Farbe wählen", "setListColorPopup-title": "Farbe wählen", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Alle Listen, Karten, Labels und Akivitäten werden gelöscht und Sie können die Inhalte des Boards nicht wiederherstellen! Die Aktion kann nicht rückgängig gemacht werden.", "boardDeletePopup-title": "Board löschen?", "delete-board": "Board löschen", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Teilaufgabe für __board__ Board", @@ -942,13 +905,6 @@ "authentication-method": "Authentifizierungsmethode", "authentication-type": "Authentifizierungstyp", "custom-product-name": "Benutzerdefinierter Produktname", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Verstecke Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "hat Ende geändert auf", "a-startAt": "hat Startzeit geändert auf", "a-receivedAt": "hat Empfangszeit geändert auf", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "aktuelles Fälligkeitsdatum %s bevorstehend", "pastdue": "aktuelles Fälligkeitsdatum %s überschritten", "duenow": "aktuelles Fälligkeitsdatum %s heute", @@ -989,7 +943,7 @@ "act-almostdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist bevorstehend", "act-pastdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist vorbei", "act-duenow": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist jetzt", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Sie wurden in [__board__] __list__/__card__ erwähnt", "delete-user-confirm-popup": "Sind Sie sicher, dass Sie diesen Account löschen wollen? Die Aktion kann nicht rückgängig gemacht werden.", "delete-team-confirm-popup": "Sind Sie sicher, dass Sie dieses Team löschen möchten? Es gibt kein Zurück!", "delete-org-confirm-popup": "Sind Sie sicher, dass Sie diese Organisation löschen möchten? Es gibt kein Zurück!", @@ -1013,7 +967,6 @@ "view-all": "Alle anzeigen", "filter-by-unread": "Nur ungelesene", "mark-all-as-read": "Alle als gelesen markieren", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Alle gelesenen entfernen", "allow-rename": "Umbenennen erlauben", "allowRenamePopup-title": "Umbenennen erlauben", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "Meine Karten", "card": "Karte", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Liste", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Zeit", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "abgeschlossen", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Bestätigen", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/de.i18n.json b/imports/i18n/data/de.i18n.json index b5c249a1b..f138b8d6c 100644 --- a/imports/i18n/data/de.i18n.json +++ b/imports/i18n/data/de.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "löschte Kommentar %s", "activity-receivedDate": "hat Empfangsdatum zu %s geändert auf %s", "activity-startDate": "hat Startdatum zu %s geändert auf %s", - "allboards.starred": "Starred", - "allboards.templates": "Vorlagen", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "hat Fälligkeitsdatum zu %s geändert auf %s", "activity-endDate": "hat Enddatum zu %s geändert auf %s", "add-attachment": "Datei anhängen", @@ -98,7 +86,6 @@ "add-card": "Karte hinzufügen", "add-card-to-top-of-list": "Karte am Anfang der Liste hinzufügen", "add-card-to-bottom-of-list": "Karte am Ende der Liste hinzufügen", - "addListPopup-title": "Liste hinzufügen", "setListWidthPopup-title": "Setze die Breiten", "set-list-width": "Setze die Breiten", "set-list-width-value": "Setze min & max Breite (Pixel)", @@ -122,10 +109,10 @@ "add-after-list": "Unter der Liste hinzufügen", "add-members": "Mitglieder hinzufügen", "added": "Hinzugefügt", - "addMemberPopup-title": "Mitglieder hinzufügen", + "addMemberPopup-title": "Mitglieder", "memberPopup-title": "Nutzereinstellungen", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Kann Karten anzeigen und bearbeiten, Mitglieder entfernen und Boardeinstellungen ändern.", "admin-announcement": "Ankündigung", "admin-announcement-active": "Aktive systemweite Ankündigungen", "admin-announcement-title": "Ankündigung des Administrators", @@ -167,16 +154,12 @@ "board-background-image-url": "Hintergrundbild URL", "add-background-image": "Hintergrundbild hinzufügen", "remove-background-image": "Hintergrundbild entfernen", - "show-at-all-boards-page": "Auf der \"Alle Boards\" Seite anzeigen", - "board-info-on-my-boards": "Einstellungen für Alle Boards", - "boardInfoOnMyBoardsPopup-title": "Einstellungen für Alle Boards", + "show-at-all-boards-page" : "Auf der \"Alle Boards\" Seite anzeigen", + "board-info-on-my-boards" : "Einstellungen für Alle Boards", + "boardInfoOnMyBoardsPopup-title" : "Einstellungen für Alle Boards", "boardInfoOnMyBoards-title": "Einstellungen für Alle Boards", "show-card-counter-per-list": "Zeige Kartenanzahl pro Liste", "show-board_members-avatar": "Zeige Profilbilder der Board-Mitglieder", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s Sterne", "board-not-found": "Board nicht gefunden", "board-private-info": "Dieses Board wird <strong>privat</strong> sein.", @@ -286,8 +269,6 @@ "change-permissions": "Berechtigungen ändern", "change-settings": "Einstellungen ändern", "changeAvatarPopup-title": "Profilbild ändern", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Sprache ändern", "changePasswordPopup-title": "Passwort ändern", "changePermissionsPopup-title": "Berechtigungen ändern", @@ -335,16 +316,10 @@ "comment-placeholder": "Kommentar schreiben", "comment-only": "Nur Kommentare", "comment-only-desc": "Kann Karten nur kommentieren.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Sind Sie sicher, dass Sie den Kommentar löschen wollen?", "deleteCommentPopup-title": "Kommentar löschen?", "no-comments": "Keine Kommentare", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Kann keine Kommentare und Aktivitäten sehen.", "worker": "Arbeiter", "worker-desc": "Kann Karten nur verschieben, sich selbst zuweisen und kommentieren.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Wollen Sie diese Checkliste wirklich löschen?", "subtaskDeletePopup-title": "Teilaufgabe löschen?", "checklistDeletePopup-title": "Checkliste löschen?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Kopiere Link zur Karte in die Zwischenablage", "copy-text-to-clipboard": "Text in die Zwischenablage kopieren", "linkCardPopup-title": "Karte verknüpfen", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Titel der ersten Karte\", \"description\":\"Beschreibung der ersten Karte\"}, {\"title\":\"Titel der zweiten Karte\",\"description\":\"Beschreibung der zweiten Karte\"},{\"title\":\"Titel der letzten Karte\",\"description\":\"Beschreibung der letzten Karte\"} ]", "create": "Erstellen", "createBoardPopup-title": "Board erstellen", - "createTemplateContainerPopup-title": "Vorlagen-Container hinzufügen", "chooseBoardSourcePopup-title": "Board importieren", "createLabelPopup-title": "Label erstellen", "createCustomField": "Feld erstellen", @@ -385,7 +358,7 @@ "date": "Datum", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Ablehnen", "default-avatar": "Standard Profilbild", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Listen können ins Archiv verschoben werden, um sie aus dem Board zu entfernen und die Aktivitäten zu behalten.", "lists": "Listen", "swimlanes": "Swimlanes", - "calendar": "Kalender", - "gantt": "Gantt", "log-out": "Ausloggen", "log-in": "Einloggen", "loginPopup-title": "Einloggen", "memberMenuPopup-title": "Nutzereinstellungen", - "grey-icons": "Grey Icons", "members": "Mitglieder", "menu": "Menü", "move-selection": "Auswahl verschieben", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Ans Ende verschieben", "moveCardToTop-title": "Zum Anfang verschieben", "moveSelectionPopup-title": "Auswahl verschieben", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Mehrfachauswahl", "multi-selection-label": "Label für die Auswahl setzen", "multi-selection-member": "Mitglied für die Auswahl setzen", @@ -587,8 +555,6 @@ "no-results": "Keine Ergebnisse", "normal": "Normal", "normal-desc": "Kann Karten anzeigen und bearbeiten, aber keine Einstellungen ändern.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Die Einladung wurde noch nicht angenommen", "notify-participate": "Benachrichtigungen zu allen Karten erhalten, bei denen Sie Ersteller oder Mitglied sind", "notify-watch": "Benachrichtigungen über alle Boards, Listen oder Karten erhalten, die Sie beobachten", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Ändern der E-Mailadresse erlauben", "accounts-allowUserNameChange": "Ändern des Benutzernamens erlauben", "tableVisibilityMode-allowPrivateOnly": "Board-Sichtbarkeit: Erlaube ausschließlich private Boards", - "tableVisibilityMode": "Sichtbarkeit der Boards", + "tableVisibilityMode" : "Sichtbarkeit der Boards", "createdAt": "Erstellt am", "modifiedAt": "Geändert am", "verified": "Geprüft", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Empfangsdatum ändern", "editCardEndDatePopup-title": "Enddatum ändern", "setCardColorPopup-title": "Farbe festlegen", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Farbe wählen", "setSwimlaneColorPopup-title": "Farbe wählen", "setListColorPopup-title": "Farbe wählen", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Alle Listen, Karten, Labels und Akivitäten werden gelöscht und Sie können die Inhalte des Boards nicht wiederherstellen! Die Aktion kann nicht rückgängig gemacht werden.", "boardDeletePopup-title": "Board löschen?", "delete-board": "Board löschen", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Teilaufgabe für __board__ Board", @@ -942,13 +905,6 @@ "authentication-method": "Authentifizierungsmethode", "authentication-type": "Authentifizierungstyp", "custom-product-name": "Benutzerdefinierter Produktname", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Verstecke Logo", "hide-card-counter-list": "Verberge die Kartenzähler-Liste auf allen Boards", @@ -979,8 +935,6 @@ "a-endAt": "hat Ende geändert auf", "a-startAt": "hat Startzeit geändert auf", "a-receivedAt": "hat Empfangszeit geändert auf", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "aktuelles Fälligkeitsdatum %s bevorstehend", "pastdue": "aktuelles Fälligkeitsdatum %s überschritten", "duenow": "aktuelles Fälligkeitsdatum %s heute", @@ -989,7 +943,7 @@ "act-almostdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist bevorstehend", "act-pastdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist vorbei", "act-duenow": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist jetzt", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Sie wurden in [__board__] __list__/__card__ erwähnt", "delete-user-confirm-popup": "Möchten Sie dieses Benutzerkonto wirklich löschen? Die Aktion kann nicht rückgängig gemacht werden.", "delete-team-confirm-popup": "Sind Sie sicher, daß Sie dieses Team löschen wollen? Es gibt keine Möglichkeit, das rückgängig zu machen.", "delete-org-confirm-popup": "Sind Sie sicher, daß Sie diese Organisation löschen wollen? Es gibt keine Möglichkeit, das rückgängig zu machen.", @@ -1013,7 +967,6 @@ "view-all": "Alle anzeigen", "filter-by-unread": "Nur ungelesene", "mark-all-as-read": "Alle als gelesen markieren", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Alle gelesenen entfernen", "allow-rename": "Umbenennen erlauben", "allowRenamePopup-title": "Umbenennen erlauben", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "Meine Karten", "card": "Karte", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Liste", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Verberge alle Checklisteneinträge", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Bedienungshilfe", "accessibility-page-enabled": "Bedienungshilfe Seite freigeschaltet", "accessibility-info-not-added-yet": "Es wurde noch keine Information zur Bedienungshilfe hinzugefügt", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Zeit", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Abgeschlossen", - "idle": "Leerlauf", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Abgeschlossen", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "abgeschlossen", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Gewicht", - "cron": "Zeitplan", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Bestätigen", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Leerlauf", + "complete": "Abgeschlossen", + "cron": "Zeitplan" } diff --git a/imports/i18n/data/de_DE.i18n.json b/imports/i18n/data/de_DE.i18n.json index 76831c0da..621bbaae0 100644 --- a/imports/i18n/data/de_DE.i18n.json +++ b/imports/i18n/data/de_DE.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "löschte Kommentar %s", "activity-receivedDate": "hat Empfangsdatum zu %s geändert auf %s", "activity-startDate": "hat Startdatum zu %s geändert auf %s", - "allboards.starred": "Markiert", - "allboards.templates": "Vorlagen", - "allboards.remaining": "Verbleibend", - "allboards.workspaces": "Arbeitsplatz", - "allboards.add-workspace": "Arbeitsplatz hinzufügen", - "allboards.add-workspace-prompt": "Arbeitsplatzname", - "allboards.add-subworkspace": "Teilarbeitsplatz hinzufügen", - "allboards.add-subworkspace-prompt": "Teilarbeitsplatzname", - "allboards.edit-workspace": "Arbeitsplatz ändern", - "allboards.edit-workspace-name": "Arbeitsplatzname", - "allboards.edit-workspace-icon": "Arbeitsplatzsymbol (markdown)", - "multi-selection-active": "Wähle Kontrollkästchen um Bretter auszuwählen", "activity-dueDate": "hat Fälligkeitsdatum zu %s geändert auf %s", "activity-endDate": "hat Enddatum zu %s geändert auf %s", "add-attachment": "Datei anhängen", @@ -98,7 +86,6 @@ "add-card": "Karte hinzufügen", "add-card-to-top-of-list": "Karte am Anfang der Liste hinzufügen", "add-card-to-bottom-of-list": "Karte am Ende der Liste hinzufügen", - "addListPopup-title": "Liste hinzufügen", "setListWidthPopup-title": "Setze Breite", "set-list-width": "Setze Breite", "set-list-width-value": "Setze min & max Breite (Pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Unter der Liste hinzufügen", "add-members": "Mitglieder hinzufügen", "added": "Hinzugefügt", - "addMemberPopup-title": "Mitglieder hinzufügen", + "addMemberPopup-title": "Mitglieder", "memberPopup-title": "Nutzereinstellungen", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Kann Karten anzeigen und bearbeiten, Mitglieder entfernen und Boardeinstellungen ändern.", "admin-announcement": "Ankündigung", "admin-announcement-active": "Aktive systemweite Ankündigungen", "admin-announcement-title": "Ankündigung des Administrators", @@ -167,16 +154,12 @@ "board-background-image-url": "Hintergrundbild URL", "add-background-image": "Hintergrundbild hinzufügen", "remove-background-image": "Hintergrundbild entfernen", - "show-at-all-boards-page": "Auf der \"Alle Boards\" Seite anzeigen", - "board-info-on-my-boards": "Einstellungen für Alle Boards", - "boardInfoOnMyBoardsPopup-title": "Einstellungen für Alle Boards", + "show-at-all-boards-page" : "Auf der \"Alle Boards\" Seite anzeigen", + "board-info-on-my-boards" : "Einstellungen für Alle Boards", + "boardInfoOnMyBoardsPopup-title" : "Einstellungen für Alle Boards", "boardInfoOnMyBoards-title": "Einstellungen für Alle Boards", "show-card-counter-per-list": "Zeige Kartenanzahl pro Liste", "show-board_members-avatar": "Zeige Profilbilder der Board-Mitglieder", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s Sterne", "board-not-found": "Board nicht gefunden", "board-private-info": "Dieses Board wird <strong>privat</strong> sein.", @@ -286,8 +269,6 @@ "change-permissions": "Berechtigungen ändern", "change-settings": "Einstellungen ändern", "changeAvatarPopup-title": "Profilbild ändern", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Sprache ändern", "changePasswordPopup-title": "Passwort ändern", "changePermissionsPopup-title": "Berechtigungen ändern", @@ -335,16 +316,10 @@ "comment-placeholder": "Kommentar schreiben", "comment-only": "Nur Kommentare", "comment-only-desc": "Kann Karten nur kommentieren.", - "comment-assigned-only": "Nur zugewiesener Kommentar", - "comment-assigned-only-desc": "Nur zugewiesene Karten sichtbar. Nur Kommentar möglich.", "comment-delete": "Sind Sie sicher, dass Sie den Kommentar löschen wollen?", "deleteCommentPopup-title": "Kommentar löschen?", "no-comments": "Keine Kommentare", - "no-comments-desc": "Can not see comments.", - "read-only": "Nur lesen", - "read-only-desc": "Kann Karten nur sehen, nicht bearbeiten.", - "read-assigned-only": "Nur Zugewiesene lesen.", - "read-assigned-only-desc": "Nur zugewiesene Karten sichtbar. Keine Änderung.", + "no-comments-desc": "Kann keine Kommentare und Aktivitäten sehen.", "worker": "Arbeiter", "worker-desc": "Kann Karten nur verschieben, sich selbst zuweisen und kommentieren.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Wollen Sie diese Checkliste wirklich löschen?", "subtaskDeletePopup-title": "Teilaufgabe löschen?", "checklistDeletePopup-title": "Checkliste löschen?", - "checklistItemDeletePopup-title": "Element der Checkliste löschen?", "copy-card-link-to-clipboard": "Kopiere Link zur Karte in die Zwischenablage", "copy-text-to-clipboard": "Text in die Zwischenablage kopieren", "linkCardPopup-title": "Karte verknüpfen", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Titel der ersten Karte\", \"description\":\"Beschreibung der ersten Karte\"}, {\"title\":\"Titel der zweiten Karte\",\"description\":\"Beschreibung der zweiten Karte\"},{\"title\":\"Titel der letzten Karte\",\"description\":\"Beschreibung der letzten Karte\"} ]", "create": "Erstellen", "createBoardPopup-title": "Board erstellen", - "createTemplateContainerPopup-title": "Vorlagen-Container hinzufügen", "chooseBoardSourcePopup-title": "Board importieren", "createLabelPopup-title": "Label erstellen", "createCustomField": "Feld erstellen", @@ -385,7 +358,7 @@ "date": "Datum", "date-format": "Datumsformat", "date-format-yyyy-mm-dd": "JJJJ-MM-TT hh:mm", - "date-format-dd-mm-yyyy": "TT-MM-JJJJ", + "date-format-dd-mm-yyyy": "TT-MM-JJJJ", "date-format-mm-dd-yyyy": "MM-TT-JJJJ", "decline": "Ablehnen", "default-avatar": "Standard Profilbild", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Listen können ins Archiv verschoben werden, um sie aus dem Board zu entfernen und die Aktivitäten zu behalten.", "lists": "Listen", "swimlanes": "Swimlanes", - "calendar": "Kalender", - "gantt": "Gantt", "log-out": "Ausloggen", "log-in": "Einloggen", "loginPopup-title": "Einloggen", "memberMenuPopup-title": "Nutzereinstellungen", - "grey-icons": "Graue Symbole", "members": "Mitglieder", "menu": "Menü", "move-selection": "Auswahl verschieben", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Ans Ende verschieben", "moveCardToTop-title": "Zum Anfang verschieben", "moveSelectionPopup-title": "Auswahl verschieben", - "copySelectionPopup-title": "Auswahl kopieren", - "selection-color": "Auswahlfarbe", "multi-selection": "Mehrfachauswahl", "multi-selection-label": "Label für die Auswahl setzen", "multi-selection-member": "Mitglied für die Auswahl setzen", @@ -587,8 +555,6 @@ "no-results": "Keine Ergebnisse", "normal": "Normal", "normal-desc": "Kann Karten anzeigen und bearbeiten, aber keine Einstellungen ändern.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Nur zugewiesene Karten sichtbar. Änderung als normaler Benutzer.", "not-accepted-yet": "Die Einladung wurde noch nicht angenommen", "notify-participate": "Benachrichtigungen zu allen Karten erhalten, bei denen Sie Ersteller oder Mitglied sind", "notify-watch": "Benachrichtigungen über alle Boards, Listen oder Karten erhalten, die Sie beobachten", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Ändern der E-Mailadresse erlauben", "accounts-allowUserNameChange": "Ändern des Benutzernamens erlauben", "tableVisibilityMode-allowPrivateOnly": "Board-Sichtbarkeit: Erlaube ausschließlich private Boards", - "tableVisibilityMode": "Sichtbarkeit der Boards", + "tableVisibilityMode" : "Sichtbarkeit der Boards", "createdAt": "Erstellt am", "modifiedAt": "Geändert am", "verified": "Geprüft", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Empfangsdatum ändern", "editCardEndDatePopup-title": "Enddatum ändern", "setCardColorPopup-title": "Farbe festlegen", - "setSelectionColorPopup-title": "Auswahlfarbe setzen", "setCardActionsColorPopup-title": "Farbe wählen", "setSwimlaneColorPopup-title": "Farbe wählen", "setListColorPopup-title": "Farbe wählen", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Alle Listen, Karten, Labels und Akivitäten werden gelöscht und Sie können die Inhalte des Boards nicht wiederherstellen! Die Aktion kann nicht rückgängig gemacht werden.", "boardDeletePopup-title": "Board löschen?", "delete-board": "Board löschen", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Lösche doppelte Listen", "delete-duplicate-lists-confirm": "Sicher? Es werden alle doppelten Listen gelöscht, die den gleichen Namen haben und keine Karten enthalten.", "default-subtasks-board": "Teilaufgabe für __board__ Board", @@ -942,13 +905,6 @@ "authentication-method": "Authentifizierungsmethode", "authentication-type": "Authentifizierungstyp", "custom-product-name": "Benutzerdefinierter Produktname", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Verstecke Logo", "hide-card-counter-list": "Verberge die Kartenzähler-Liste auf allen Boards", @@ -979,8 +935,6 @@ "a-endAt": "hat Ende geändert auf", "a-startAt": "hat Startzeit geändert auf", "a-receivedAt": "hat Empfangszeit geändert auf", - "above-selected-card": "Oben ausgewählte Karte", - "below-selected-card": "Unten ausgewählte Karte", "almostdue": "aktuelles Fälligkeitsdatum %s bevorstehend", "pastdue": "aktuelles Fälligkeitsdatum %s überschritten", "duenow": "aktuelles Fälligkeitsdatum %s heute", @@ -989,7 +943,7 @@ "act-almostdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist bevorstehend", "act-pastdue": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist vorbei", "act-duenow": "erinnernd an das aktuelle Fälligkeitszeitpunkt (__timeValue__) von __card__ ist jetzt", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Sie wurden in [__board__] __list__/__card__ erwähnt", "delete-user-confirm-popup": "Möchten Sie dieses Benutzerkonto wirklich löschen? Die Aktion kann nicht rückgängig gemacht werden.", "delete-team-confirm-popup": "Sind Sie sicher, daß Sie dieses Team löschen wollen? Es gibt keine Möglichkeit, das rückgängig zu machen.", "delete-org-confirm-popup": "Sind Sie sicher, daß Sie diese Organisation löschen wollen? Es gibt keine Möglichkeit, das rückgängig zu machen.", @@ -1013,7 +967,6 @@ "view-all": "Alle anzeigen", "filter-by-unread": "Nur ungelesene", "mark-all-as-read": "Alle als gelesen markieren", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Alle gelesenen entfernen", "allow-rename": "Umbenennen erlauben", "allowRenamePopup-title": "Umbenennen erlauben", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "Meine Karten", "card": "Karte", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Liste", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Verberge alle Checklisteneinträge", "support": "Unterstützung", "supportPopup-title": "Unterstützung", - "support-page-enabled": "Supportseite eingeschaltet", - "support-info-not-added-yet": "Supportinfo wurde noch nicht hinzugefügt", - "support-info-only-for-logged-in-users": "Supportinfo ist nur für angemeldete Benutzer", - "support-title": "Supporttitel", - "support-content": "Supportinhalt", "accessibility": "Bedienungshilfe", "accessibility-page-enabled": "Barrierefreie Seite freigeschaltet", "accessibility-info-not-added-yet": "Es wurde noch keine Information zur Bedienungshilfe hinzugefügt", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Geplante Aufgabe erfolgreich gelöscht", "cron-job-pause-failed": "Anhalten der geplanten Aufgabe fehlgeschlagen", "cron-job-paused": "Geplante Aufgabe erfolgreich angehalten", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Zeit", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Vollständig", - "idle": "Untätig", "filesystem-path-description": "Basispfad des Dateispeichers", "gridfs-enabled": "GridFS aktiviert", "gridfs-enabled-description": "Benutze MongoDB GridFS als Dateispeicher", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Unterbrechung der Migrationen fehlgeschlagen", "migration-paused": "Migrationen erfolgreich unterbrochen", "migration-progress": "Migrationsfortschritt", "migration-start-failed": "Start der Migrationen fehlgeschlagen", "migration-started": "Migrationen erfolgreich gestartet", - "migration-not-needed": "No migration needed", "migration-status": "Migrationsstatus", "migration-stop-confirm": "Sind Sie sicher, dass Sie alle Migrationen stoppen wollen?", "migration-stop-failed": "Stoppen der Migrationen fehlgeschlagen", @@ -1490,73 +1404,7 @@ "back-to-settings": "Zurück zu den Einstellungen", "board-id": "Brett ID", "board-migration": "Brettmigration", - "board-migrations": "Brettmigrationen", "card-show-lists-on-minicard": "Zeige Listen auf der Minikarte", - "comprehensive-board-migration": "Umfassende Brettmigration", - "comprehensive-board-migration-description": "Führt umfassende Überprüfungen und Korrekturen für die Integrität der Board-Daten durch, einschließlich Listensortierung, Kartenpositionen und Swimlane-Struktur.", - "delete-duplicate-empty-lists-migration": "Lösche doppelte leere Listen", - "delete-duplicate-empty-lists-migration-description": "Löscht sicher leere doppelte Listen. Entfernt nur Listen, die keine Karten haben UND eine andere Liste mit dem gleichen Titel haben, der Karten enthält.", - "lost-cards": "Verlorene Karten", - "lost-cards-list": "Wiederhergestelle Artikel", - "restore-lost-cards-migration": "Verlorene Karten wiederherstellen", - "restore-lost-cards-migration-description": "Findet Karten und Listen mit fehlender swimlaneId oder listID und stellt sie wieder her. Erstellt eine 'LostCards' Swimlane um die verlorenen Dinge wieder sichtbar zu machen.", - "restore-all-archived-migration": "Alles Archivierte wiederherstellen", - "restore-all-archived-migration-description": "Stellt alle archivierten Swimmlanes, Listen und Karten wieder her. Repariert automatisch jede fehlende swimlaneId oder listId um sie sichtbar zu machen.", - "fix-missing-lists-migration": "Repariere fehlende Listen", - "fix-missing-lists-migration-description": "Entdeckt und repariert fehlende oder defekte Listen in der Brettstruktur.", - "fix-avatar-urls-migration": "Repariere Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Vollständig", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Gesamtfortschritt", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Repariere Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Lösche doppelte leere Listen", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Aufräumen", "cleanup-old-jobs": "Alte Aufgaben aufräumen", "completed": "abgeschlossen", @@ -1637,7 +1485,6 @@ "schedule": "Zeitplanung", "search-boards-or-operations": "Durchsuche Bretter oder Vorgänge", "show-list-on-minicard": "Zeige Liste auf der Minikarte", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Gezeigt", "start-test-operation": "Starte Testvorgang", "start-time": "Startzeit", @@ -1650,37 +1497,7 @@ "total-size": "Gesamte Größe", "unmigrated-boards": "Nicht migrierte Bretter", "weight": "Gewicht", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Bestätigen", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Untätig", + "complete": "Vollständig", + "cron": "Cron" } diff --git a/imports/i18n/data/el-GR.i18n.json b/imports/i18n/data/el-GR.i18n.json index 0b1f4d96e..48fcea53a 100644 --- a/imports/i18n/data/el-GR.i18n.json +++ b/imports/i18n/data/el-GR.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "διεγράφη το σχόλιο %s", "activity-receivedDate": "η ημερομηνία λήψης άλλαξε σε %s από %s", "activity-startDate": "η ημερομηνία έναρξης άλλαξε σε %s από %s", - "allboards.starred": "Starred", - "allboards.templates": "Πρότυπα", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "υπέστη επεξεργασία η τιμή της προθεσμίας σε %s από %s", "activity-endDate": "η ημερομηνία λήξης άλλαξε σε %s από %s", "add-attachment": "Προσθήκη Συνημμένου", @@ -98,7 +86,6 @@ "add-card": "Προσθήκη Κάρτας", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Προσθήκη Λίστας", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Προσθήκη Μελών", "added": "Προστέθηκε", - "addMemberPopup-title": "Προσθήκη Μελών", + "addMemberPopup-title": "Μέλη", "memberPopup-title": "Ρυθμίσεις Μελών", "admin": "Διαχειριστής", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Μπορεί να δει, να επεξεργαστεί κάρτες, να διαγράψει μέλη και να μεταβάλει τις ρυθμίσεις του πίνακα.", "admin-announcement": "Ανακοίνωση", "admin-announcement-active": "Ενεργή Ανακοίνωση που είναι ορατή σε όλο το σύστημα", "admin-announcement-title": "Ανακοίνωση από το Διαχειριστή Συστήματος", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s αστέρια", "board-not-found": "Ο πίνακας δε βρέθηκε", "board-private-info": "Αυτός ο πίνακας θα είναι <strong>κρυφός</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Αλλαγή δικαιωμάτων", "change-settings": "Αλλαγή Ρυθμίσεων", "changeAvatarPopup-title": "Αλλαγή Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Αλλαγή Γλώσσας", "changePasswordPopup-title": "Αλλαγή Κωδικού", "changePermissionsPopup-title": "Αλλαγή Δικαιωμάτων", @@ -335,16 +316,10 @@ "comment-placeholder": "Συγγραφή Σχολίου", "comment-only": "Μόνο σχόλιο", "comment-only-desc": "Μπορεί μόνο να σχολιάζει σε κάρτες.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "Χωρίς σχόλια", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Δε μπορεί να δει σχόλια και δραστηριότητες.", "worker": "Worker", "worker-desc": "Μπορεί μόνο να μετακινεί κάρτες, να αναθέτει μια κάρτα στον εαυτό του και να σχολιάζει.", "computer": "Υπολογιστής", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Αντιγραφή του συνδέσμου της κάρτας στο clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Σύνδεση Κάρτας", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Τίτλος πρώτης κάρτας\", \"description\":\"Περιγραφή πρώτης κάρτας\"}, {\"title\":\"Τίτλος δεύτερης κάρτας\",\"description\":\"Περιγραφή δεύτερης κάρτας\"},{\"title\":\"Τίτλος τελευταίας κάρτας\",\"description\":\"Περιγραφή τελευταίας κάρτας\"} ]", "create": "Δημιουργία", "createBoardPopup-title": "Δημιουργία Πίνακα", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Εισαγωγή πίνακα", "createLabelPopup-title": "Δημιουργία Ετικέτας", "createCustomField": "Δημιουργία Πεδίου", @@ -385,7 +358,7 @@ "date": "Ημερομηνία", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Απόρριψη", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Μπορείτε να μετακινήσετε μια λίστα στο Αρχείο για να την αφαιρέσετε από τον πίνακα και να διατηρήσετε τη δραστηριότητα.", "lists": "Λίστες", "swimlanes": "Λωρίδες", - "calendar": "Ημερολόγιο", - "gantt": "Διάγραμμα Gantt", "log-out": "Αποσύνδεση", "log-in": "Σύνδεση", "loginPopup-title": "Σύνδεση", "memberMenuPopup-title": "Ρυθμίσεις Μελών", - "grey-icons": "Grey Icons", "members": "Μέλη", "menu": "Μενού", "move-selection": "Μετακίνηση επιλογής", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Μετακίνηση στην Αρχή", "moveCardToTop-title": "Μετακίνηση στο Τέλος", "moveSelectionPopup-title": "Μετακίνηση επιλογής", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Πολλαπλή Επιλογή", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "Κανένα αποτέλεσμα", "normal": "Κανονικό", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Η πρόσκληση δεν έχει λάβει αποδοχή ακόμη", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Επίτρεψε Αλλαγή Email", "accounts-allowUserNameChange": "Επίτρεψε Αλλαγή Ονόματος χρήστη", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Δημιουργήθηκε στις", "modifiedAt": "Modified at", "verified": "Επιβεβαιώθηκε", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Αλλαγή ημερομηνίας λήψης", "editCardEndDatePopup-title": "Αλλαγή ημερομηνίας λήξης", "setCardColorPopup-title": "Ρύθμιση χρώματος", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Επιλέξτε ένα χρώμα", "setSwimlaneColorPopup-title": "Επιλέξτε ένα χρώμα", "setListColorPopup-title": "Επιλέξτε ένα χρώμα", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Διαγραφή Πίνακα;", "delete-board": "Διαγραφή Πίνακα", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Κάρτα", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Ώρα", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Έναρξη", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/el.i18n.json b/imports/i18n/data/el.i18n.json index 0a7978328..c4da6359d 100644 --- a/imports/i18n/data/el.i18n.json +++ b/imports/i18n/data/el.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "διεγράφη το σχόλιο %s", "activity-receivedDate": "η ημερομηνία λήψης άλλαξε σε %s από %s", "activity-startDate": "η ημερομηνία έναρξης άλλαξε σε %s από %s", - "allboards.starred": "Starred", - "allboards.templates": "Πρότυπα", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "υπέστη επεξεργασία η τιμή της προθεσμίας σε %s από %s", "activity-endDate": "η ημερομηνία λήξης άλλαξε σε %s από %s", "add-attachment": "Προσθήκη Συνημμένου", @@ -98,7 +86,6 @@ "add-card": "Προσθήκη Κάρτας", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Προσθήκη Λίστας", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Προσθήκη Μελών", "added": "Προστέθηκε", - "addMemberPopup-title": "Προσθήκη Μελών", + "addMemberPopup-title": "Μέλη", "memberPopup-title": "Ρυθμίσεις Μελών", "admin": "Διαχειριστής", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Μπορεί να δει, να επεξεργαστεί κάρτες, να διαγράψει μέλη και να μεταβάλει τις ρυθμίσεις του πίνακα.", "admin-announcement": "Ανακοίνωση", "admin-announcement-active": "Ενεργή Ανακοίνωση που είναι ορατή σε όλο το σύστημα", "admin-announcement-title": "Ανακοίνωση από το Διαχειριστή Συστήματος", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s αστέρια", "board-not-found": "Ο πίνακας δε βρέθηκε", "board-private-info": "Αυτός ο πίνακας θα είναι <strong>κρυφός</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Αλλαγή δικαιωμάτων", "change-settings": "Αλλαγή Ρυθμίσεων", "changeAvatarPopup-title": "Αλλαγή Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Αλλαγή Γλώσσας", "changePasswordPopup-title": "Αλλαγή Κωδικού", "changePermissionsPopup-title": "Αλλαγή Δικαιωμάτων", @@ -335,16 +316,10 @@ "comment-placeholder": "Συγγραφή Σχολίου", "comment-only": "Μόνο σχόλιο", "comment-only-desc": "Μπορεί μόνο να σχολιάζει σε κάρτες.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "Χωρίς σχόλια", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Δε μπορεί να δει σχόλια και δραστηριότητες.", "worker": "Worker", "worker-desc": "Μπορεί μόνο να μετακινεί κάρτες, να αναθέτει μια κάρτα στον εαυτό του και να σχολιάζει.", "computer": "Υπολογιστής", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Αντιγραφή του συνδέσμου της κάρτας στο clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Σύνδεση Κάρτας", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Τίτλος πρώτης κάρτας\", \"description\":\"Περιγραφή πρώτης κάρτας\"}, {\"title\":\"Τίτλος δεύτερης κάρτας\",\"description\":\"Περιγραφή δεύτερης κάρτας\"},{\"title\":\"Τίτλος τελευταίας κάρτας\",\"description\":\"Περιγραφή τελευταίας κάρτας\"} ]", "create": "Δημιουργία", "createBoardPopup-title": "Δημιουργία Πίνακα", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Εισαγωγή πίνακα", "createLabelPopup-title": "Δημιουργία Ετικέτας", "createCustomField": "Δημιουργία Πεδίου", @@ -385,7 +358,7 @@ "date": "Ημερομηνία", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Απόρριψη", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Μπορείτε να μετακινήσετε μια λίστα στο Αρχείο για να την αφαιρέσετε από τον πίνακα και να διατηρήσετε τη δραστηριότητα.", "lists": "Λίστες", "swimlanes": "Λωρίδες", - "calendar": "Ημερολόγιο", - "gantt": "Διάγραμμα Gantt", "log-out": "Αποσύνδεση", "log-in": "Σύνδεση", "loginPopup-title": "Σύνδεση", "memberMenuPopup-title": "Ρυθμίσεις Μελών", - "grey-icons": "Grey Icons", "members": "Μέλη", "menu": "Μενού", "move-selection": "Μετακίνηση επιλογής", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Μετακίνηση στην Αρχή", "moveCardToTop-title": "Μετακίνηση στο Τέλος", "moveSelectionPopup-title": "Μετακίνηση επιλογής", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Πολλαπλή Επιλογή", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "Κανένα αποτέλεσμα", "normal": "Κανονικό", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Η πρόσκληση δεν έχει λάβει αποδοχή ακόμη", "notify-participate": "Receive updates to any cards you participate as creater or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Επίτρεψε Αλλαγή Email", "accounts-allowUserNameChange": "Επίτρεψε Αλλαγή Ονόματος χρήστη", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Δημιουργήθηκε στις", "modifiedAt": "Modified at", "verified": "Επιβεβαιώθηκε", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Αλλαγή ημερομηνίας λήψης", "editCardEndDatePopup-title": "Αλλαγή ημερομηνίας λήξης", "setCardColorPopup-title": "Ρύθμιση χρώματος", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Επιλέξτε ένα χρώμα", "setSwimlaneColorPopup-title": "Επιλέξτε ένα χρώμα", "setListColorPopup-title": "Επιλέξτε ένα χρώμα", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Διαγραφή Πίνακα;", "delete-board": "Διαγραφή Πίνακα", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Κάρτα", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Ώρα", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Έναρξη", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/en-BR.i18n.json b/imports/i18n/data/en-BR.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/en-BR.i18n.json +++ b/imports/i18n/data/en-BR.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/en-DE.i18n.json b/imports/i18n/data/en-DE.i18n.json index e19043776..50ddae1d2 100644 --- a/imports/i18n/data/en-DE.i18n.json +++ b/imports/i18n/data/en-DE.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/en-GB.i18n.json b/imports/i18n/data/en-GB.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/en-GB.i18n.json +++ b/imports/i18n/data/en-GB.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/en-IT.i18n.json b/imports/i18n/data/en-IT.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/en-IT.i18n.json +++ b/imports/i18n/data/en-IT.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/en-MY.i18n.json b/imports/i18n/data/en-MY.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/en-MY.i18n.json +++ b/imports/i18n/data/en-MY.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/en-YS.i18n.json b/imports/i18n/data/en-YS.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/en-YS.i18n.json +++ b/imports/i18n/data/en-YS.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/en.i18n.json b/imports/i18n/data/en.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/en.i18n.json +++ b/imports/i18n/data/en.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/en_AU.i18n.json b/imports/i18n/data/en_AU.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/en_AU.i18n.json +++ b/imports/i18n/data/en_AU.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/en_ID.i18n.json b/imports/i18n/data/en_ID.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/en_ID.i18n.json +++ b/imports/i18n/data/en_ID.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/en_SG.i18n.json b/imports/i18n/data/en_SG.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/en_SG.i18n.json +++ b/imports/i18n/data/en_SG.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/en_TR.i18n.json b/imports/i18n/data/en_TR.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/en_TR.i18n.json +++ b/imports/i18n/data/en_TR.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/en_ZA.i18n.json b/imports/i18n/data/en_ZA.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/en_ZA.i18n.json +++ b/imports/i18n/data/en_ZA.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/eo.i18n.json b/imports/i18n/data/eo.i18n.json index 80c2ec297..20a85f9f6 100644 --- a/imports/i18n/data/eo.i18n.json +++ b/imports/i18n/data/eo.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Aldoni membrojn", "added": "Aldonita", - "addMemberPopup-title": "Aldoni membrojn", + "addMemberPopup-title": "Membroj", "memberPopup-title": "Membraj agordoj", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Ŝanĝi agordojn", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Ŝanĝi lingvon", "changePasswordPopup-title": "Ŝangi pasvorton", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Komputilo", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Krei", "createBoardPopup-title": "Krei tavolon", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Dato", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Listoj", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Elsaluti", "log-in": "Ensaluti", "loginPopup-title": "Ensaluti", "memberMenuPopup-title": "Membraj agordoj", - "grey-icons": "Grey Icons", "members": "Membroj", "menu": "Menuo", "move-selection": "Movi elekton", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Movi suben", "moveCardToTop-title": "Movi supren", "moveSelectionPopup-title": "Movi elekton", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "Neniaj rezultoj", "normal": "Normala", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Tempo", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Komenco", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/es-AR.i18n.json b/imports/i18n/data/es-AR.i18n.json index 6d70119c0..874d60c15 100644 --- a/imports/i18n/data/es-AR.i18n.json +++ b/imports/i18n/data/es-AR.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "comentario %s eliminado", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Plantillas", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Agregar Adjunto", @@ -98,7 +86,6 @@ "add-card": "Agregar Tarjeta", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Agregar Lista", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Agregar Miembros", "added": "Agregadas", - "addMemberPopup-title": "Agregar Miembros", + "addMemberPopup-title": "Miembros", "memberPopup-title": "Opciones de Miembros", "admin": "Administrador", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Puede ver y editar tarjetas, eliminar miembros, y cambiar opciones para el tablero.", "admin-announcement": "Anuncio", "admin-announcement-active": "Anuncio del Sistema Activo", "admin-announcement-title": "Anuncio del Administrador", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s estrellas", "board-not-found": "Tablero no encontrado", "board-private-info": "Este tablero va a ser <strong>privado</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Cambiar permisos", "change-settings": "Cambiar Opciones", "changeAvatarPopup-title": "Cambiar Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Cambiar Lenguaje", "changePasswordPopup-title": "Cambiar Contraseña", "changePermissionsPopup-title": "Cambiar Permisos", @@ -335,16 +316,10 @@ "comment-placeholder": "Comentar", "comment-only": "Comentar solamente", "comment-only-desc": "Puede comentar en tarjetas solamente.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "Sin comentarios", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computadora", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copiar enlace a tarjeta en el portapapeles", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Tarjeta vinculada", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Título de primera tarjeta\", \"description\":\"Descripción de primera tarjeta\"}, {\"title\":\"Título de segunda tarjeta\",\"description\":\"Descripción de segunda tarjeta\"},{\"title\":\"Título de última tarjeta\",\"description\":\"Descripción de última tarjeta\"} ]", "create": "Crear", "createBoardPopup-title": "Crear Tablero", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Importar tablero", "createLabelPopup-title": "Crear Etiqueta", "createCustomField": "Crear Campo", @@ -385,7 +358,7 @@ "date": "Fecha", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Rechazar", "default-avatar": "Avatar por defecto", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Listas", "swimlanes": "Calles", - "calendar": "Calendario", - "gantt": "Gantt", "log-out": "Salir", "log-in": "Entrar", "loginPopup-title": "Entrar", "memberMenuPopup-title": "Opciones de Miembros", - "grey-icons": "Grey Icons", "members": "Miembros", "menu": "Menú", "move-selection": "Mover selección", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Mover al Final", "moveCardToTop-title": "Mover al Tope", "moveSelectionPopup-title": "Mover selección", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selección", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No hay resultados", "normal": "Normal", "normal-desc": "Puede ver y editar tarjetas. No puede cambiar opciones.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitación no aceptada todavía", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Recibí actualizaciones en cualquier tablero, lista, o tarjeta que estés siguiendo", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Permitir Cambio de Email", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Creado en", "modifiedAt": "Modified at", "verified": "Verificado", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Cambiar fecha de recepción", "editCardEndDatePopup-title": "Cambiar fecha de término", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Tarjeta", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Hora", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Empieza", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/es-CL.i18n.json b/imports/i18n/data/es-CL.i18n.json index 0486e90a6..6b167dc52 100644 --- a/imports/i18n/data/es-CL.i18n.json +++ b/imports/i18n/data/es-CL.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "comentario eliminado", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Plantillas", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Añadir adjunto", @@ -98,7 +86,6 @@ "add-card": "Añadir una tarjeta", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Añadir una lista", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Añadir miembros", "added": "Añadida el", - "addMemberPopup-title": "Añadir miembros", + "addMemberPopup-title": "Miembros", "memberPopup-title": "Preferencias de miembro", "admin": "Administrador", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Puedes ver y editar tarjetas, eliminar miembros, y cambiar las preferencias del tablero", "admin-announcement": "Aviso", "admin-announcement-active": "Activar el aviso para todo el sistema", "admin-announcement-title": "Aviso del administrador", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s destacados", "board-not-found": "Tablero no encontrado", "board-private-info": "Este tablero será <strong>privado</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Cambiar los permisos", "change-settings": "Cambiar las preferencias", "changeAvatarPopup-title": "Cambiar el avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Cambiar el idioma", "changePasswordPopup-title": "Cambiar la contraseña", "changePermissionsPopup-title": "Cambiar los permisos", @@ -335,16 +316,10 @@ "comment-placeholder": "Escribir comentario", "comment-only": "Sólo comentarios", "comment-only-desc": "Solo puedes comentar en las tarjetas.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No hay comentarios", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "No se pueden mostrar comentarios ni actividades.", "worker": "Trabajador", "worker-desc": "Solo puede mover tarjetas, asignarse a la tarjeta y comentar.", "computer": "el ordenador", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copiar el enlace de la tarjeta al portapapeles", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Enlazar tarjeta", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Título de la primera tarjeta\", \"description\":\"Descripción de la primera tarjeta\"}, {\"title\":\"Título de la segunda tarjeta\",\"description\":\"Descripción de la segunda tarjeta\"},{\"title\":\"Título de la última tarjeta\",\"description\":\"Descripción de la última tarjeta\"} ]", "create": "Crear", "createBoardPopup-title": "Crear tablero", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Importar un tablero", "createLabelPopup-title": "Crear una etiqueta", "createCustomField": "Crear un campo", @@ -385,7 +358,7 @@ "date": "Fecha", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Declinar", "default-avatar": "Avatar por defecto", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Puedes mover una lista al Archivo para quitarla del tablero y preservar la actividad.", "lists": "Listas", "swimlanes": "Carriles", - "calendar": "Calendario", - "gantt": "Gantt", "log-out": "Finalizar la sesión", "log-in": "Iniciar sesión", "loginPopup-title": "Iniciar sesión", "memberMenuPopup-title": "Preferencias de miembro", - "grey-icons": "Grey Icons", "members": "Miembros", "menu": "Menú", "move-selection": "Mover la selección", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Mover al final", "moveCardToTop-title": "Mover al principio", "moveSelectionPopup-title": "Mover la selección", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Selección múltiple", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "Sin resultados", "normal": "Normal", "normal-desc": "Puedes ver y editar tarjetas. No puedes cambiar la configuración.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "La invitación no ha sido aceptada aún", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Recibir actuaizaciones de cualquier tablero, lista o tarjeta que estés vigilando", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Permitir cambiar el correo electrónico", "accounts-allowUserNameChange": "Permitir cambiar el nombre de usuario", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Fecha de alta", "modifiedAt": "Modified at", "verified": "Verificado", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Cambiar la fecha de recepción", "editCardEndDatePopup-title": "Cambiar la fecha de finalización", "setCardColorPopup-title": "Cambiar el color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Elegir un color", "setSwimlaneColorPopup-title": "Elegir un color", "setListColorPopup-title": "Elegir un color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Se eliminarán todas las listas, tarjetas, etiquetas y actividades, y no podrás recuperar los contenidos del tablero. Esta acción no puede deshacerse.", "boardDeletePopup-title": "¿Eliminar el tablero?", "delete-board": "Eliminar el tablero", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtareas para el tablero __board__", @@ -942,13 +905,6 @@ "authentication-method": "Método de autenticación", "authentication-type": "Tipo de autenticación", "custom-product-name": "Nombre de producto personalizado", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Diseño", "hide-logo": "Ocultar el logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "cambiada la hora de finalización a", "a-startAt": "cambiada la hora de comienzo a", "a-receivedAt": "cambiada la hora de recepción a", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "está próxima la hora de vencimiento actual %s", "pastdue": "se sobrepasó la hora de vencimiento actual%s", "duenow": "la hora de vencimiento actual %s es hoy", @@ -989,7 +943,7 @@ "act-almostdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ está próximo", "act-pastdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ se sobrepasó", "act-duenow": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ es ahora", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Se te mencionó en [__board__] __list__/__card__", "delete-user-confirm-popup": "¿Seguro que quieres eliminar esta cuenta? Esta acción no puede deshacerse.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "Ver todo", "filter-by-unread": "Filtrar por no leído", "mark-all-as-read": "Marcar todo como leido", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Permitir renombrar", "allowRenamePopup-title": "Permitir renombrar", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Tarjeta", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Hora", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Comienza", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completada", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/es-LA.i18n.json b/imports/i18n/data/es-LA.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/es-LA.i18n.json +++ b/imports/i18n/data/es-LA.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/es-MX.i18n.json b/imports/i18n/data/es-MX.i18n.json index 2faa1dc0c..858a623c9 100644 --- a/imports/i18n/data/es-MX.i18n.json +++ b/imports/i18n/data/es-MX.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "comentario eliminado %s", "activity-receivedDate": "fecha de recepción editada para %s de %s", "activity-startDate": "fecha de inicio editada a %s de %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "fecha de vencimiento editada a %s de %s", "activity-endDate": "editada la fecha de finalización a %s de %s", "add-attachment": "Agregar adjunto", @@ -98,7 +86,6 @@ "add-card": "Agregar Tarjeta", "add-card-to-top-of-list": "Agregar tarjeta al inicio de la lista", "add-card-to-bottom-of-list": "Agregar tarjeta al final de la lista", - "addListPopup-title": "Agregar Lista", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Agregar miembros", "added": "Agregado", - "addMemberPopup-title": "Agregar miembros", + "addMemberPopup-title": "Miembros", "memberPopup-title": "Member Settings", "admin": "Administrador", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Puede ver y editar tarjetas, eliminar miembros y cambiar la configuración del tablero.", "admin-announcement": "Anuncio", "admin-announcement-active": "Habilitar los anuncios en todo el sistema", "admin-announcement-title": "Anuncio del administrador", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Miembros", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/es-PE.i18n.json b/imports/i18n/data/es-PE.i18n.json index dcc39cb0b..276b1d368 100644 --- a/imports/i18n/data/es-PE.i18n.json +++ b/imports/i18n/data/es-PE.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "comentario eliminado", "activity-receivedDate": "editada la fecha de recepción a %s de %s", "activity-startDate": "editada la fecha de inicio a %s de %s", - "allboards.starred": "Starred", - "allboards.templates": "Plantillas", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "editada la fecha de vencimiento a %s de %s", "activity-endDate": "editada la fecha de finalización a %s de %s", "add-attachment": "Agregar adjunto", @@ -98,7 +86,6 @@ "add-card": "Agregar una tarjeta", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Agregar una lista", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Agregar miembros", "added": "Agregada el", - "addMemberPopup-title": "Agregar miembros", + "addMemberPopup-title": "Miembros", "memberPopup-title": "Configuración de miembros", "admin": "Administrador", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Puede ver y editar tarjetas, eliminar miembros y cambiar la configuración del tablero.", "admin-announcement": "Anuncio", "admin-announcement-active": "Habilitar los anuncios en todo el sistema", "admin-announcement-title": "Anuncio del administrador", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s destacados", "board-not-found": "Tablero no encontrado", "board-private-info": "Este tablero será <strong>privado</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Cambiar los permisos", "change-settings": "Cambiar la configuración", "changeAvatarPopup-title": "Cambiar el avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Cambiar el idioma", "changePasswordPopup-title": "Cambiar la contraseña", "changePermissionsPopup-title": "Cambiar los permisos", @@ -335,16 +316,10 @@ "comment-placeholder": "Escribir comentario", "comment-only": "Sólo comentarios", "comment-only-desc": "Sólo puede comentar en las tarjetas.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No hay comentarios", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "No se pueden mostrar comentarios ni actividades.", "worker": "Trabajador", "worker-desc": "Sólo puede mover tarjetas, asignarse a la tarjeta y comentar.", "computer": "Computadora", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copiar el enlace de la tarjeta al portapapeles", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Enlazar tarjeta", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Título de la primera tarjeta\", \"description\":\"Descripción de la primera tarjeta\"}, {\"title\":\"Título de la segunda tarjeta\",\"description\":\"Descripción de la segunda tarjeta\"},{\"title\":\"Título de la última tarjeta\",\"description\":\"Descripción de la última tarjeta\"} ]", "create": "Crear", "createBoardPopup-title": "Crear tablero", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Importar un tablero", "createLabelPopup-title": "Crear una etiqueta", "createCustomField": "Crear un campo", @@ -385,7 +358,7 @@ "date": "Fecha", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Declinar", "default-avatar": "Avatar por defecto", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Puede mover una lista a archivo para eliminarla del tablero y preservar la actividad.", "lists": "Listas", "swimlanes": "Carriles", - "calendar": "Calendario", - "gantt": "Gantt", "log-out": "Finalizar la sesión", "log-in": "Iniciar sesión", "loginPopup-title": "Iniciar sesión", "memberMenuPopup-title": "Configuración de miembros", - "grey-icons": "Grey Icons", "members": "Miembros", "menu": "Menú", "move-selection": "Mover la selección", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Mover al final", "moveCardToTop-title": "Mover al principio", "moveSelectionPopup-title": "Mover la selección", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Selección múltiple", "multi-selection-label": "Establecer etiqueta para la selección", "multi-selection-member": "Establecer miembro para la selección", @@ -587,8 +555,6 @@ "no-results": "Sin resultados", "normal": "Normal", "normal-desc": "Puede ver y editar las tarjetas. No puede cambiar la configuración.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "La invitación no ha sido aceptada aún", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Recibir actualizaciones de cualquier tablero, lista o tarjeta que esté vigilando", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Permitir cambiar el correo electrónico", "accounts-allowUserNameChange": "Permitir cambiar el nombre de usuario", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Creado en", "modifiedAt": "Modified at", "verified": "Verificado", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Cambiar la fecha de recepción", "editCardEndDatePopup-title": "Cambiar la fecha de finalización", "setCardColorPopup-title": "Cambiar el color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Elegir un color", "setSwimlaneColorPopup-title": "Elegir un color", "setListColorPopup-title": "Elegir un color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Se eliminarán todas las listas, tarjetas, etiquetas y actividades, y no podrás recuperar los contenidos del tablero. Esta acción no puede deshacerse.", "boardDeletePopup-title": "¿Eliminar el tablero?", "delete-board": "Eliminar el tablero", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtareas para el tablero __board__", @@ -942,13 +905,6 @@ "authentication-method": "Método de autenticación", "authentication-type": "Tipo de autenticación", "custom-product-name": "Nombre de producto personalizado", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Diseño", "hide-logo": "Ocultar el logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "cambiada la hora de finalización a", "a-startAt": "cambiada la hora de inicio a", "a-receivedAt": "cambiada la hora de recepción a", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "está próxima la hora de vencimiento actual %s", "pastdue": "se sobrepasó la hora de vencimiento actual %s", "duenow": "la hora de vencimiento actual %s es hoy", @@ -989,7 +943,7 @@ "act-almostdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ está próximo", "act-pastdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ se sobrepasó", "act-duenow": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ es ahora", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Se le mencionó en [__board__] __list__/__card__", "delete-user-confirm-popup": "¿Seguro que desea eliminar esta cuenta? Esta acción no puede deshacerse.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "Ver todo", "filter-by-unread": "Filtrar por no leído", "mark-all-as-read": "Marcar todo como leido", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Eliminar todos los leídos", "allow-rename": "Permitir renombrar", "allowRenamePopup-title": "Permitir renombrar", @@ -1048,10 +1001,6 @@ "person": "Persona", "my-cards": "Mis tarjetas", "card": "Tarjeta", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Lista", "board": "Tablero", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Hora", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Comienza", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Estado", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completada", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/es-PY.i18n.json b/imports/i18n/data/es-PY.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/es-PY.i18n.json +++ b/imports/i18n/data/es-PY.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/es.i18n.json b/imports/i18n/data/es.i18n.json index 6c864cdbb..c0ea65cd5 100644 --- a/imports/i18n/data/es.i18n.json +++ b/imports/i18n/data/es.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "comentario eliminado", "activity-receivedDate": "editada la fecha de recepción a %s de %s", "activity-startDate": "editada la fecha de inicio a %s de %s", - "allboards.starred": "Starred", - "allboards.templates": "Plantillas", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "editada la fecha de vencimiento a %s de %s", "activity-endDate": "editada la fecha de finalización a %s de %s", "add-attachment": "Añadir adjunto", @@ -98,7 +86,6 @@ "add-card": "Añadir una tarjeta", "add-card-to-top-of-list": "Subir la tarjeta al principio de la lista", "add-card-to-bottom-of-list": "Bajar la tarjeta al final de la lista", - "addListPopup-title": "Añadir una lista", "setListWidthPopup-title": "Ajustar anchuras", "set-list-width": "Ajustar anchuras", "set-list-width-value": "Establecer anchos mín. y máx. (píxeles)", @@ -122,10 +109,10 @@ "add-after-list": "Añadir después de la lista", "add-members": "Añadir miembros", "added": "Añadida el", - "addMemberPopup-title": "Añadir miembros", + "addMemberPopup-title": "Miembros", "memberPopup-title": "Preferencias de miembro", "admin": "Administrador", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Puedes ver y editar tarjetas, eliminar miembros, y cambiar las preferencias del tablero", "admin-announcement": "Aviso", "admin-announcement-active": "Activar el aviso para todo el sistema", "admin-announcement-title": "Anuncio del administrador", @@ -167,16 +154,12 @@ "board-background-image-url": "URL de la imagen de fondo", "add-background-image": "Añadir imagen de fondo", "remove-background-image": "Quitar imagen de fondo", - "show-at-all-boards-page": "Mostrar todos los tableros", - "board-info-on-my-boards": "Configuración de todos los tableros", - "boardInfoOnMyBoardsPopup-title": "Configuración de todos los tableros", + "show-at-all-boards-page" : "Mostrar todos los tableros", + "board-info-on-my-boards" : "Configuración de todos los tableros", + "boardInfoOnMyBoardsPopup-title" : "Configuración de todos los tableros", "boardInfoOnMyBoards-title": "Configuración de todos los tableros", "show-card-counter-per-list": "Mostrar el contador de tarjetas por lista", "show-board_members-avatar": "Mostrar los avatares de los miembros del tablero", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s destacados", "board-not-found": "Tablero no encontrado", "board-private-info": "Este tablero será <strong>privado</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Cambiar los permisos", "change-settings": "Cambiar las preferencias", "changeAvatarPopup-title": "Cambiar el avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Cambiar el idioma", "changePasswordPopup-title": "Cambiar la contraseña", "changePermissionsPopup-title": "Cambiar los permisos", @@ -335,16 +316,10 @@ "comment-placeholder": "Escribir comentario", "comment-only": "Sólo comentarios", "comment-only-desc": "Solo puedes comentar en las tarjetas.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "¿Estás seguro que quieres borrar el comentario?", "deleteCommentPopup-title": "¿Borrar comentario?", "no-comments": "No hay comentarios", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "No se pueden mostrar comentarios ni actividades.", "worker": "Trabajador", "worker-desc": "Solo puede mover tarjetas, asignarse a la tarjeta y comentar.", "computer": "el ordenador", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "¿Estás seguro de querer eliminar la lista de tareas?", "subtaskDeletePopup-title": "¿Borrar subtarea?", "checklistDeletePopup-title": "¿Borrar la lista de tareas?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copiar el enlace de la tarjeta al portapapeles", "copy-text-to-clipboard": "Copiar texto en el portapapeles", "linkCardPopup-title": "Enlazar tarjeta", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Título de la primera tarjeta\", \"description\":\"Descripción de la primera tarjeta\"}, {\"title\":\"Título de la segunda tarjeta\",\"description\":\"Descripción de la segunda tarjeta\"},{\"title\":\"Título de la última tarjeta\",\"description\":\"Descripción de la última tarjeta\"} ]", "create": "Crear", "createBoardPopup-title": "Crear tablero", - "createTemplateContainerPopup-title": "añadir plantilla de contenedor", "chooseBoardSourcePopup-title": "Importar un tablero", "createLabelPopup-title": "Crear una etiqueta", "createCustomField": "Crear un campo", @@ -385,7 +358,7 @@ "date": "Fecha", "date-format": "Formato de fecha", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Declinar", "default-avatar": "Avatar por defecto", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Puedes mover una lista al Archivo para quitarla del tablero y preservar la actividad.", "lists": "Listas", "swimlanes": "Carriles", - "calendar": "Calendario", - "gantt": "Gantt", "log-out": "Finalizar la sesión", "log-in": "Iniciar sesión", "loginPopup-title": "Iniciar sesión", "memberMenuPopup-title": "Preferencias de miembro", - "grey-icons": "Grey Icons", "members": "Miembros", "menu": "Menú", "move-selection": "Mover la selección", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Mover al final", "moveCardToTop-title": "Mover al principio", "moveSelectionPopup-title": "Mover la selección", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Selección múltiple", "multi-selection-label": "Establecer etiqueta para la selección", "multi-selection-member": "Establecer miembro para la selección", @@ -587,8 +555,6 @@ "no-results": "Sin resultados", "normal": "Normal", "normal-desc": "Puedes ver y editar tarjetas. No puedes cambiar la configuración.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "La invitación no ha sido aceptada aún", "notify-participate": "Recibir actualizaciones de cualquier tarjeta en la que participes como creador o miembro", "notify-watch": "Recibir actuaizaciones de cualquier tablero, lista o tarjeta que estés vigilando", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Permitir cambiar el correo electrónico", "accounts-allowUserNameChange": "Permitir cambiar el nombre de usuario", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Fecha de alta", "modifiedAt": "Modified at", "verified": "Verificado", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Cambiar la fecha de recepción", "editCardEndDatePopup-title": "Cambiar la fecha de finalización", "setCardColorPopup-title": "Cambiar el color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Elegir un color", "setSwimlaneColorPopup-title": "Elegir un color", "setListColorPopup-title": "Elegir un color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Se eliminarán todas las listas, tarjetas, etiquetas y actividades, y no podrás recuperar los contenidos del tablero. Esta acción no puede deshacerse.", "boardDeletePopup-title": "¿Eliminar el tablero?", "delete-board": "Eliminar el tablero", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtareas para el tablero __board__", @@ -942,13 +905,6 @@ "authentication-method": "Método de autenticación", "authentication-type": "Tipo de autenticación", "custom-product-name": "Nombre de producto personalizado", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Diseño", "hide-logo": "Ocultar el logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "cambiada la hora de finalización a", "a-startAt": "cambiada la hora de comienzo a", "a-receivedAt": "cambiada la hora de recepción a", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "está próxima la hora de vencimiento actual %s", "pastdue": "se sobrepasó la hora de vencimiento actual%s", "duenow": "la hora de vencimiento actual %s es hoy", @@ -989,7 +943,7 @@ "act-almostdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ está próximo", "act-pastdue": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ se sobrepasó", "act-duenow": "se ha notificado que el vencimiento actual (__timeValue__) de __card__ es ahora", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Se te mencionó en [__board__] __list__/__card__", "delete-user-confirm-popup": "¿Estás seguro de querer eliminar esta cuenta? Esta acción no puede deshacerse.", "delete-team-confirm-popup": "¿Estás seguro de querer eliminar este equipo? Esta acción no puede deshacerse", "delete-org-confirm-popup": "¿Estás seguro de querer eliminar esta organización? Esta acción no puede deshacerse.", @@ -1013,7 +967,6 @@ "view-all": "Ver todo", "filter-by-unread": "Filtrar por no leído", "mark-all-as-read": "Marcar todo como leido", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Eliminar todos los leídos", "allow-rename": "Permitir renombrar", "allowRenamePopup-title": "Permitir renombrar", @@ -1048,10 +1001,6 @@ "person": "Persona", "my-cards": "Mis Tarjetas", "card": "Tarjeta", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Lista", "board": "Tablero", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Ocultar todos los elementos de la lista de verificación", "support": "Soporte", "supportPopup-title": "Soporte", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Página de accesibilidad habilitada", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Hora", - "cron-error-message": "Error Message", - "cron-error-details": "Detalles", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Completado", - "idle": "Inactivo", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Comienza", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Fallo al pausar las migraciones", "migration-paused": "Migrations paused successfully", "migration-progress": "Proceso de migración", "migration-start-failed": "Fallo al iniciar las migraciones", "migration-started": "Las migraciones se han iniciado correctamente", - "migration-not-needed": "No migration needed", "migration-status": "Estado de migración", "migration-stop-confirm": "¿Estás seguro de querer detener todas las migraciones?", "migration-stop-failed": "Fallo al detener las migraciones", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Completado", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Estado", - "migration-progress-details": "Detalles", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completada", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Peso", - "cron": "Programación", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirmar", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Inactivo", + "complete": "Completado", + "cron": "Programación" } diff --git a/imports/i18n/data/es_CO.i18n.json b/imports/i18n/data/es_CO.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/es_CO.i18n.json +++ b/imports/i18n/data/es_CO.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/et-EE.i18n.json b/imports/i18n/data/et-EE.i18n.json index 5b5b4f476..40bf924c4 100644 --- a/imports/i18n/data/et-EE.i18n.json +++ b/imports/i18n/data/et-EE.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "kustutatud kommentaar %s", "activity-receivedDate": "redigeeritud saabunud kuupäev %s-i %s-i", "activity-startDate": "redigeeritud alguskuupäev %s-i %s-i alguskuupäevaks", - "allboards.starred": "Starred", - "allboards.templates": "Mallid", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "redigeeritud tähtaeg on %s of %s", "activity-endDate": "redigeeritud lõpukuupäev %s-i %s-i lõpukuupäevaks", "add-attachment": "Lisa lisa", @@ -98,7 +86,6 @@ "add-card": "Lisa kaart", "add-card-to-top-of-list": "Kaardi lisamine nimekirja tippu", "add-card-to-bottom-of-list": "Lisa kaart nimekirja lõppu", - "addListPopup-title": "Lisa nimekiri", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Liikmete lisamine", "added": "Lisatud", - "addMemberPopup-title": "Liikmete lisamine", + "addMemberPopup-title": "Liikmed", "memberPopup-title": "Liikme seaded", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Saab vaadata ja muuta kaarte, eemaldada liikmeid ja muuta juhatuse seadeid.", "admin-announcement": "Teadaanne", "admin-announcement-active": "Kogu süsteemi hõlmav aktiivne teade", "admin-announcement-title": "Administraatori teade", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s tähed", "board-not-found": "Juhatust ei leitud", "board-private-info": "See foorum on <strong>privaatne</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Õiguste muutmine", "change-settings": "Seadete muutmine", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Keele muutmine", "changePasswordPopup-title": "Muuda salasõna", "changePermissionsPopup-title": "Õiguste muutmine", @@ -335,16 +316,10 @@ "comment-placeholder": "Kirjutage kommentaar", "comment-only": "Ainult kommentaar", "comment-only-desc": "Saab kommenteerida ainult kaarte.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Kas olete kindel, et soovite kommentaari kustutada?", "deleteCommentPopup-title": "Kuidas kustutada?", "no-comments": "Ei kommentaare", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Ei näe kommentaare ja tegevusi.", "worker": "Töötaja", "worker-desc": "Saab ainult liigutada kaarte, määrata ennast kaardile ja kommenteerida.", "computer": "Arvuti", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Kas olete kindel, et soovite kontrollnimekirja kustutada?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Kustuta kontrollnimekiri?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Kopeeri kaardi link lõikelauale", "copy-text-to-clipboard": "Teksti kopeerimine lõikelauale", "linkCardPopup-title": "Linkkaart", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": {\"title\": \"First card title\", \"description\": \"First card description\"}, {\"title\": \"Second card title\", \"description\": \"Second card description\"},{\"title\": \"Last card title\", \"description\": \"Last card description\"}, {\"title\": \"Last card title\", \"description\": \"Last card description\"} ]", "create": "Loo", "createBoardPopup-title": "Loo juhatus", - "createTemplateContainerPopup-title": "Malli konteineri lisamine", "chooseBoardSourcePopup-title": "Impordilaua", "createLabelPopup-title": "Loo silt", "createCustomField": "Loo väli", @@ -385,7 +358,7 @@ "date": "Kuupäev", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Langus", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Saate listi liigutada arhiivi, et eemaldada see tahvlilt ja säilitada tegevus.", "lists": "Loetelud", "swimlanes": "Swimlanes", - "calendar": "Kalender", - "gantt": "Gantt", "log-out": "Logi välja", "log-in": "Logi sisse", "loginPopup-title": "Logi sisse", "memberMenuPopup-title": "Liikme seaded", - "grey-icons": "Grey Icons", "members": "Liikmed", "menu": "Menüü", "move-selection": "Liikumise valik", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Liigu allapoole", "moveCardToTop-title": "Liigu ülespoole", "moveSelectionPopup-title": "Liikumise valik", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Mitmikvalik", "multi-selection-label": "Valiku sildi määramine", "multi-selection-member": "Valiku liige valiku tegemiseks", @@ -587,8 +555,6 @@ "no-results": "Tulemusi ei ole", "normal": "Tavaline", "normal-desc": "Saab vaadata ja redigeerida kaarte. Ei saa seadistusi muuta.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Kutse ei ole veel vastu võetud", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Saate uuendusi kõikidest tahvlitest, nimekirjadest või kaartidest, mida jälgite.", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Lubage e-posti muutmine", "accounts-allowUserNameChange": "Lubage kasutajanime muutmine", "tableVisibilityMode-allowPrivateOnly": "Tahvlite nähtavus: Lubage ainult privaatseid tahvleid", - "tableVisibilityMode": "Tahvlite nähtavus", + "tableVisibilityMode" : "Tahvlite nähtavus", "createdAt": "Loodud aadressil", "modifiedAt": "Muudetud aadressil", "verified": "Kinnitatud", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Muuda saadud kuupäeva", "editCardEndDatePopup-title": "Muuda lõppkuupäeva", "setCardColorPopup-title": "Värvi määramine", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Vali värv", "setSwimlaneColorPopup-title": "Vali värv", "setListColorPopup-title": "Vali värv", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Kõik nimekirjad, kaardid, sildid ja tegevused kustutatakse ja te ei saa tahvli sisu taastada. Tühistamist ei ole võimalik teha.", "boardDeletePopup-title": "Kustuta juhatus?", "delete-board": "Kustuta juhatus", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Juhatuse __board__ alamülesanded", @@ -942,13 +905,6 @@ "authentication-method": "Autentimismeetod", "authentication-type": "Autentimise tüüp", "custom-product-name": "Kohandatud toote nimi", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Paigutus", "hide-logo": "Peida logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "muudetud lõpuaeg, et olla", "a-startAt": "muudetud algusaeg on", "a-receivedAt": "muudetud saadud aeg, et olla", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "praegune tähtaeg %s läheneb", "pastdue": "praegune tähtaeg %s on möödas", "duenow": "praegune tähtaeg %s on täna", @@ -989,7 +943,7 @@ "act-almostdue": "tuletas meelde, et __kaardi__ praegune tähtaeg (__timeValue__) läheneb.", "act-pastdue": "tuletas meelde, et __kaardi__ praegune tähtaeg (__timeValue__) on möödas.", "act-duenow": "tuletas meelde, et __kaardi__ praegune tähtaeg (__timeValue__) on praegu", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Teid mainiti [__board__] __list__/__card__'is__", "delete-user-confirm-popup": "Kas olete kindel, et soovite selle konto kustutada? Tühistamist ei ole võimalik teha.", "delete-team-confirm-popup": "Kas olete kindel, et soovite selle meeskonna kustutada? Tühistamist ei ole võimalik teha.", "delete-org-confirm-popup": "Kas olete kindel, et soovite selle organisatsiooni kustutada? Tühistamist ei ole võimalik teha.", @@ -1013,7 +967,6 @@ "view-all": "Vaata kõiki", "filter-by-unread": "Filtreeri lugemata järgi", "mark-all-as-read": "Märgi kõik loetud", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Eemaldage kõik loetud", "allow-rename": "Luba ümbernimetamine", "allowRenamePopup-title": "Luba ümbernimetamine", @@ -1048,10 +1001,6 @@ "person": "Isik", "my-cards": "Minu kaardid", "card": "Kaart", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Loetelu", "board": "Juhatus", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Aeg", - "cron-error-message": "Error Message", - "cron-error-details": "Üksikasjad", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Staatus", - "migration-progress-details": "Üksikasjad", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Lõpetatud", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Kinnitage", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/eu.i18n.json b/imports/i18n/data/eu.i18n.json index 042d3168a..84dd9440d 100644 --- a/imports/i18n/data/eu.i18n.json +++ b/imports/i18n/data/eu.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "%s iruzkina ezabatu da", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Izarduna", - "allboards.templates": "Txantiloiak", - "allboards.remaining": "Faltan", - "allboards.workspaces": "Laneko area", - "allboards.add-workspace": "Gehitu laneko area", - "allboards.add-workspace-prompt": "Laneko arearen izena", - "allboards.add-subworkspace": "Gehitu azpiko laneko area", - "allboards.add-subworkspace-prompt": "Azpiko laneko arearen izena", - "allboards.edit-workspace": "Editatu laneko area", - "allboards.edit-workspace-name": "Laneko arearen izena", - "allboards.edit-workspace-icon": "Laneko arearen ikonoa (markdown)", - "multi-selection-active": "Klikatu kontrol laukiak arbelak hautatzeko", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Gehitu eranskina", @@ -98,7 +86,6 @@ "add-card": "Gehitu txartela", "add-card-to-top-of-list": "Gehitu txartela zerrendaren goiko aldean", "add-card-to-bottom-of-list": "Gehitu txartela zerrendaren beheko aldean", - "addListPopup-title": "Gehitu zerrenda", "setListWidthPopup-title": "Ezarri zabalerak", "set-list-width": "Ezarri zabalerak", "set-list-width-value": "Ezarri gutxieneko eta gehieneko zabalerak (pixel)", @@ -122,10 +109,10 @@ "add-after-list": "Gehitu zerrendaren ondoren", "add-members": "Gehitu kideak", "added": "Gehituta", - "addMemberPopup-title": "Gehitu kideak", + "addMemberPopup-title": "Kideak", "memberPopup-title": "Kidearen ezarpenak", "admin": "Kudeatzailea", - "admin-desc": "Txartelak ikusi eta editatu ditzakezu, kideak ezabatu eta baita arbelaren ezarpenak aldatu. Aktibitateak ikus ditzakezu.", + "admin-desc": "Txartelak ikusi eta editatu ditzake, kideak kendu, eta arbelaren ezarpenak aldatu.", "admin-announcement": "Jakinarazpena", "admin-announcement-active": "Gaitu Sistema-eremuko Jakinarazpena", "admin-announcement-title": "Administrariaren jakinarazpena", @@ -167,16 +154,12 @@ "board-background-image-url": "Atzeko planoko irudiaren URLa", "add-background-image": "Gehitu atzeko planoko irudia", "remove-background-image": "Kendu atzeko planoko irudia", - "show-at-all-boards-page": "Erakutsi Arbel Guztien orrian", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", - "boardInfoOnMyBoards-title": "Arbel guztien ezarpenak", + "show-at-all-boards-page" : "Erakutsi Arbel Guztien orrian", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", + "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Erakutsi txartel kopurua zerrenda bakoitzeko", "show-board_members-avatar": "Erakutsi arbeleko kideen avatarrak", - "board_members": "Arbelaren kide guztiak", - "card_members": "Arbel honetako egungo txartelaren kide guztiak", - "board_assignees": "Arbel honetako txartel guztien esleipendun guztiak", - "card_assignees": "Arbel honetako egungo txartelaren esleipendun guztiak", "board-nb-stars": "%s izar", "board-not-found": "Ez da arbela aurkitu", "board-private-info": "Arbel hau <strong>pribatua</strong> izango da.", @@ -286,8 +269,6 @@ "change-permissions": "Aldatu baimenak", "change-settings": "Aldatu ezarpenak", "changeAvatarPopup-title": "Aldatu avatarra", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Aldatu hizkuntza", "changePasswordPopup-title": "Aldatu pasahitza", "changePermissionsPopup-title": "Aldatu baimenak", @@ -335,16 +316,10 @@ "comment-placeholder": "Idatzi iruzkin bat", "comment-only": "Iruzkinak besterik ez", "comment-only-desc": "Iruzkinak txarteletan soilik egin daitezke", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Ziur zaude iruzkina ezabatu nahi duzula?", "deleteCommentPopup-title": "Ezabatu iruzkina?", "no-comments": "Iruzkinik ez", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Ezin dira iruzkinak eta aktibitateak ikusi.", "worker": "Langilea", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Ordenagailua", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Ziur kontrol-zerrenda ezabatu nahi duzula?", "subtaskDeletePopup-title": "Ezabatu azpi-ataza?", "checklistDeletePopup-title": "Ezabatu kontrol-zerrenda?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Kopiatu txartela arbelera", "copy-text-to-clipboard": "Kopiatu testua arbelean", "linkCardPopup-title": "Estekatu txartela", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Sortu", "createBoardPopup-title": "Sortu arbela", - "createTemplateContainerPopup-title": "Gehitu txantiloien edukiontzia", "chooseBoardSourcePopup-title": "Inportatu arbela", "createLabelPopup-title": "Sortu etiketa", "createCustomField": "Sortu eremua", @@ -385,7 +358,7 @@ "date": "Data", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Ukatu", "default-avatar": "Lehenetsitako avatarra", @@ -510,12 +483,12 @@ "from-csv": "CSV/TSV-tik", "import-board-instruction-trello": "Zure Trello arbelean, aukeratu 'Menu\", 'More', 'Print and Export', 'Export JSON', eta kopiatu jasotako testua hemen.", "import-board-instruction-csv": "Itsatsi zure Comma Separated Values(CSV)/ Tab Separated Values (TSV)-an .", - "import-board-instruction-wekan": "Zure arbelean, joan 'Menua' atalera, ondoren 'Esportatu arbela', eta kopiatu testua deskargatutako fitxategian.", - "import-board-instruction-about-errors": "Arbela inportatzerakoan erroreak jasotzen badituzu, batzuetan inportatzeak funtzionatzen du oraindik, eta arbele Arbela guztiak orrialdean dago.", + "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", + "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", "import-json-placeholder": "Isatsi baliozko JSON datuak hemen", "import-csv-placeholder": "Itsatsi baliozko CSV/TSV datuak hemen", "import-map-members": "Kideen mapa", - "import-members-map": "Inportatutako zure arbelak kide batzuk ditu. Mesedez, zehaztu inportatu nahi dituzun kideak zure erabiltzaileei", + "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", "import-show-user-mapping": "Berrikusi kideen mapa", "import-user-select": "Pick your existing user you want to use as this member", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Eraman ahal duzu zerrenda bat biltegira arbeletik kentzeko aktibitatea mantentzen.", "lists": "Zerrendak", "swimlanes": "Errailak", - "calendar": "Egutegia", - "gantt": "Gantt", "log-out": "Itxi saioa", "log-in": "Hasi saioa", "loginPopup-title": "Hasi saioa", "memberMenuPopup-title": "Kidearen ezarpenak", - "grey-icons": "Grey Icons", "members": "Kideak", "menu": "Menua", "move-selection": "Lekuz aldatu hautaketa", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Eraman behera", "moveCardToTop-title": "Eraman gora", "moveSelectionPopup-title": "Lekuz aldatu hautaketa", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Hautaketa anitza", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "Emaitzarik ez", "normal": "Arrunta", "normal-desc": "Txartelak ikusi eta editatu ditzake. Ezin ditu ezarpenak aldatu.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Gonbidapena ez da oraindik onartu", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Jaso ikuskatzen dituzun arbel, zerrenda edo txartelen jakinarazpenak", @@ -627,7 +593,7 @@ "search-cards": "Bilatu arbel honetako txartel/zerrenden tituluetan, deskripzioetan eta eremu pertsonalizatuetan", "search-example": "Idatzi bilatzen duzun testua eta sakatu Sartu", "select-color": "Aukeratu kolorea", - "select-board": "Hautatu arbela", + "select-board": "Select Board", "set-wip-limit-value": "Zerrenda honetako atazen muga maximoa ezarri", "setWipLimitPopup-title": "WIP muga ezarri", "shortcut-add-self": "Gehitu zeure burua uneko txartelera", @@ -766,8 +732,8 @@ "accounts": "Kontuak", "accounts-allowEmailChange": "Baimendu e-mail aldaketa", "accounts-allowUserNameChange": "Baimendu erabiltzaile-izena aldatzea", - "tableVisibilityMode-allowPrivateOnly": "Arbelen ikusgarritasuna: gaitu bakarri arbel pribatuak", - "tableVisibilityMode": "Arbelen ikusgarritasuna", + "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Noiz sortua", "modifiedAt": "Modified at", "verified": "Egiaztatuta", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Aldatu bukaerako data", "setCardColorPopup-title": "Ezarri kolorea", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Aukeratu kolore bat", "setSwimlaneColorPopup-title": "Aukeratu kolore bat", "setListColorPopup-title": "Aukeratu kolore bat", @@ -788,10 +753,8 @@ "card-sorting-by-number": "Ordenatu txartelak zenbakiaren arabera", "board-delete-notice": "Behin betiko ezabatzen du. Zerrenda guztiak, txartelak eta arbel honi lotutako aktibitate guztiak galduko dituzu.", "delete-board-confirm-popup": "Zerrenda, txartel eta aktibitate guztiak ezabatuko dira eta ezingo dituzu berreskuratu arbelaren edukiak. Atzera bueltarik ez du.", - "boardDeletePopup-title": "Arbela ezabatu nahi duzu?", - "delete-board": "Ezabatu arbela", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", + "boardDeletePopup-title": "Delete Board?", + "delete-board": "Delete Board", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -830,7 +793,7 @@ "r-rule": "Rule", "r-add-trigger": "Add trigger", "r-add-action": "Add action", - "r-board-rules": "Arbelaren arauak", + "r-board-rules": "Board rules", "r-add-rule": "Add rule", "r-view-rule": "View rule", "r-delete-rule": "Delete rule", @@ -843,7 +806,7 @@ "r-is-moved": "is moved", "r-added-to": "Added to", "r-removed-from": "Removed from", - "r-the-board": "Arbela", + "r-the-board": "the board", "r-list": "list", "set-filter": "Set Filter", "r-moved-to": "Moved to", @@ -942,13 +905,6 @@ "authentication-method": "Autentifikazio metodoa", "authentication-type": "Autentifikazio mota", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Diseinua", "hide-logo": "Ezkutatu logoa", "hide-card-counter-list": "Ezkutatu txartelen kontagailuen zerrenda arbel guztietan", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,12 +1001,8 @@ "person": "Person", "my-cards": "Nire txartelak", "card": "Txartela", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", - "board": "Arbela", + "board": "Board", "context-separator": "/", "myCardsViewChange-title": "My Cards View", "myCardsViewChangePopup-title": "My Cards View", @@ -1061,7 +1010,7 @@ "myCardsViewChange-choice-table": "Table", "myCardsSortChange-title": "Ordenatu nire txartelak", "myCardsSortChangePopup-title": "Ordenatu nire txartelak", - "myCardsSortChange-choice-board": "Arbelaren arabera", + "myCardsSortChange-choice-board": "By Board", "myCardsSortChange-choice-dueat": "By Due Date", "dueCards-title": "Txartelen epemuga", "dueCardsViewChange-title": "Txartelen epemugen ikuspegia", @@ -1072,7 +1021,7 @@ "dueCards-noResults-title": "No Due Cards Found", "dueCards-noResults-description": "You don't have any cards with due dates at the moment.", "broken-cards": "Puskatutako txartelak", - "board-title-not-found": "Ez da aurkitu '%s' arbela.", + "board-title-not-found": "Board '%s' not found.", "swimlane-title-not-found": "Swimlane '%s' not found.", "list-title-not-found": "List '%s' not found.", "label-not-found": "Label '%s' not found.", @@ -1081,12 +1030,12 @@ "comment-not-found": "Ez da aurkitu '%s' testua iruzkinetan duen txartelik..", "org-name-not-found": "Organization '%s' not found.", "team-name-not-found": "Team '%s' not found.", - "globalSearch-title": "Bilatu arbel guztiak.", + "globalSearch-title": "Search All Boards", "no-cards-found": "Ez da txartelik aurkitu", "one-card-found": "Txartel bat aurkitu da", "n-cards-found": "%s txartel aurkitu dira", "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", - "operator-board": "arbela", + "operator-board": "board", "operator-board-abbrev": "b", "operator-swimlane": "swimlane", "operator-swimlane-abbrev": "s", @@ -1208,7 +1157,7 @@ "filesReportTitle": "Files Report", "reports": "Reports", "rulesReportTitle": "Rules Report", - "boardsReportTitle": "Arbelen txostena", + "boardsReportTitle": "Boards Report", "cardsReportTitle": "Cards Report", "copy-swimlane": "Copy Swimlane", "copySwimlanePopup-title": "Copy Swimlane", @@ -1245,7 +1194,7 @@ "cardDetailsPopup-title": "Txartelaren zehetasunak", "add-teams": "Add teams", "add-teams-label": "Added teams are displayed below:", - "remove-team-from-table": "Ziur talde hau arbeletik kendu nahi duzula?", + "remove-team-from-table": "Are you sure you want to remove this team from the board ?", "confirm-btn": "Confirm", "remove-btn": "Kendu", "filter-card-title-label": "Iragazi txartelaren tituluaren arabera", @@ -1270,7 +1219,7 @@ "Node_memory_usage_external": "Node memory usage: external", "add-organizations": "Add organizations", "add-organizations-label": "Added organizations are displayed below:", - "remove-organization-from-board": "Ziur erakunde hau arbel honetatik kendu nahi duzula?", + "remove-organization-from-board": "Are you sure you want to remove this organization from this board ?", "to-create-organizations-contact-admin": "To create organizations, please contact administrator.", "custom-legal-notice-link-url": "Custom legal notice page URL", "acceptance_of_our_legalNotice": "By continuing, you accept our", @@ -1322,7 +1271,7 @@ "allowed-avatar-filetypes": "Baimendutako avatar fitxategi motak:", "invalid-file": "Fitxategi-izena baliogabea bada, karga edo izena aldaketa bertan behera geratuko da.", "preview-pdf-not-supported": "Zure gailuak ez du onartzen PDF aurrebistarik. Saiatu deskargatzen.", - "drag-board": "Arrastatu arbela", + "drag-board": "Arrastatu taula", "translation-number": "Itzulpen-kate pertsonalizatuen kopurua hau da:", "delete-translation-confirm-popup": "Ziur itzulpen-kate pertsonalizatu hau ezabatu nahi duzula? Ezin da desegin.", "newTranslationPopup-title": "Itzulpen-kate pertsonalizatu berria", @@ -1334,18 +1283,13 @@ "show-subtasks-field": "azpi-zereginen eremua bezala", "show-week-of-year": "Erakutsi rrteko aste zenbakia (ISO 8601)", "convert-to-markdown": "Bihurtu markdown-era", - "import-board-zip": "Gehitu arbeleko JSON fitxategiak dituen .zip fitxategia eta eranskinak dituzten arbelen izenen azpidirektorioak", + "import-board-zip": "Gehitu taula JSON fitxategiak dituen .zip fitxategia eta eranskinak dituzten taularen izenen azpidirektorioak", "collapse": "Tolestu", "uncollapse": "Zabaldu", "hideCheckedChecklistItems": "Ezkutatu egiaztatutako zerrendako elementuak", "hideAllChecklistItems": "Ezkutatu kontrol-zerrendako elementu guztiak", "support": "Laguntza", "supportPopup-title": "Laguntza", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "irisgarritasuna", "accessibility-page-enabled": "Irisgarritasun orria gaituta", "accessibility-info-not-added-yet": "Irisgarritasun informaziorik ez da gehitu oraindik", @@ -1389,13 +1333,13 @@ "attachments-path-description": "Path where attachment files are stored", "avatars-path": "Avatars Path", "avatars-path-description": "Path where avatar files are stored", - "board-archive-failed": "Ezin izan da programatu arbelaren archiboa", - "board-archive-scheduled": "Arbelaren artxiboa behar bezala programatu da", - "board-backup-failed": "Ezin izan da programatu arbelaren babeskopia", - "board-backup-scheduled": "Arbelaren babeskopia behar bezala programatu da", - "board-cleanup-failed": "Ezin izan da arbelaren garbiketa programatu", - "board-cleanup-scheduled": "Arbelaren garbiketa behar bezala programatu da", - "board-operations": "Arbelaren eragiketak", + "board-archive-failed": "Failed to schedule board archive", + "board-archive-scheduled": "Board archive scheduled successfully", + "board-backup-failed": "Failed to schedule board backup", + "board-backup-scheduled": "Board backup scheduled successfully", + "board-cleanup-failed": "Failed to schedule board cleanup", + "board-cleanup-scheduled": "Board cleanup scheduled successfully", + "board-operations": "Board Operations", "cron-jobs": "Scheduled Jobs", "cron-migrations": "Scheduled Migrations", "cron-job-delete-confirm": "Are you sure you want to delete this scheduled job?", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Ordua", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Hasiera", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1472,10 +1386,10 @@ "s3-ssl-enabled": "S3 SSL Enabled", "s3-ssl-enabled-description": "Use SSL/TLS for S3 connections", "save-s3-settings": "Save S3 Settings", - "schedule-board-archive": "Programatu arbelaren artxiboa", - "schedule-board-backup": "Programatu arbelaren babeskopia", - "schedule-board-cleanup": "Programatu arbelaren garbiketa", - "scheduled-board-operations": "Programatu arbelaren eragiketak", + "schedule-board-archive": "Schedule Board Archive", + "schedule-board-backup": "Schedule Board Backup", + "schedule-board-cleanup": "Schedule Board Cleanup", + "scheduled-board-operations": "Scheduled Board Operations", "start-all-migrations": "Start All Migrations", "stop-all-migrations": "Stop All Migrations", "test-s3-connection": "Test S3 Connection", @@ -1488,75 +1402,9 @@ "attachment-storage-settings": "Storage Settings", "automatic-migration": "Automatic Migration", "back-to-settings": "Back to Settings", - "board-id": "Arbelaren IDa", - "board-migration": "Arbelaren migrazioa", - "board-migrations": "Arbelen migrazioak", + "board-id": "Board ID", + "board-migration": "Board Migration", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Aztertu arbelaren egitura", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/fa-IR.i18n.json b/imports/i18n/data/fa-IR.i18n.json index 5d338d6fc..ddfb6a36d 100644 --- a/imports/i18n/data/fa-IR.i18n.json +++ b/imports/i18n/data/fa-IR.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "%s نظر حذف شد", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "قالب‌ها", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "افزودن ضمیمه", @@ -98,7 +86,6 @@ "add-card": "افزودن کارت", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "افزودن لیست", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "اضافه کردن به بعد از لیست", "add-members": "افزودن اعضا", "added": "اضافه گردید", - "addMemberPopup-title": "افزودن اعضا", + "addMemberPopup-title": "اعضا", "memberPopup-title": "تنظیمات اعضا", "admin": "مدیر", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "امکان دیدن و ویرایش کارت‌ها، حذف اعضا و تغییرِ تنظیماتِ برد.", "admin-announcement": "اعلان", "admin-announcement-active": "فعال کردن اعلان‌های سمت سیستم", "admin-announcement-title": "اعلان از سوی مدیر", @@ -167,16 +154,12 @@ "board-background-image-url": "لینک تصویر پس زمینه", "add-background-image": "اضافه کردن تصویر پس زمینه", "remove-background-image": "حذف تصویر پس زمینه", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s ستاره", "board-not-found": "برد پیدا نشد", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "تغییر دسترسی‌ها", "change-settings": "تغییر تنظیمات", "changeAvatarPopup-title": "تغییر آواتار", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "تغییر زبان", "changePasswordPopup-title": "تغییر کلمه عبور", "changePermissionsPopup-title": "تغییر دسترسی‌ها", @@ -335,16 +316,10 @@ "comment-placeholder": "درج نظر", "comment-only": "فقط نظر", "comment-only-desc": "فقط می‌تواند روی کارت‌ها نظر دهد.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "آیا مطمئنید که می خواهید این نظر را پاک کنید؟", "deleteCommentPopup-title": "نظر پاک شود؟", "no-comments": "هیچ کامنتی موجود نیست", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "نظرات و فعالیت ها را نمی توان دید.", "worker": "کارگر", "worker-desc": "تنها می‌توانید کارت‌ها را جابجا کنید، آنها را به خود محول کنید و نظر دهید.", "computer": "رایانه", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "درج پیوند کارت در حافظه", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "ارتباط دادن کارت", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "ایجاد", "createBoardPopup-title": "ایجاد برد", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "وارد کردن برد", "createLabelPopup-title": "ایجاد لیبل", "createCustomField": "ایجاد فیلد", @@ -385,7 +358,7 @@ "date": "تاریخ", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "رد", "default-avatar": "آواتار پیش‌فرض", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "شما می توانید لیست را به آرشیو انتقال دهید تا آن را از برد حذف نمایید و فعالیت های خود را حفظ نمایید", "lists": "لیست‌ها", "swimlanes": "مسیرها", - "calendar": "تقویم", - "gantt": "گانت", "log-out": "خروج", "log-in": "ورود", "loginPopup-title": "ورود", "memberMenuPopup-title": "تنظیمات اعضا", - "grey-icons": "Grey Icons", "members": "اعضا", "menu": "منو", "move-selection": "انتقال مورد انتخابی", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "انتقال به پایین", "moveCardToTop-title": "انتقال به بالا", "moveSelectionPopup-title": "انتقال مورد انتخابی", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "امکان چند انتخابی", "multi-selection-label": "تغییر لیبل انتخاب‌شده‌ها", "multi-selection-member": "تغییر عضو برای انتخاب‌شده‌ها", @@ -587,8 +555,6 @@ "no-results": "بدون نتیجه", "normal": "عادی", "normal-desc": "امکان نمایش و تنظیم کارت بدون امکان تغییر تنظیمات", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "دعوت نامه هنوز پذیرفته نشده است", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "اطلاع رسانی از هرگونه تغییر در بردها، لیست‌ها یا کارت‌هایی که از آنها دیده‌بانی می‌کنید", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "اجازه تغییر ایمیل", "accounts-allowUserNameChange": "اجازه تغییر نام کاربری", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "ساخته شده در", "modifiedAt": "تغییر یافته در", "verified": "معتبر", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "تغییر تاریخ رسید", "editCardEndDatePopup-title": "تغییر تاریخ پایان", "setCardColorPopup-title": "انتخاب رنگ", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "انتخاب کردن رنگ", "setSwimlaneColorPopup-title": "انتخاب کردن رنگ", "setListColorPopup-title": "انتخاب کردن رنگ", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "تمام لیست ها، کارت ها، لیبل ها و فعالیت ها حذف خواهند شد و شما نمی توانید محتوای برد را بازیابی کنید. هیچ واکنشی وجود ندارد", "boardDeletePopup-title": "حذف برد؟", "delete-board": "حذف برد", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "زیروظایفِ برد __board__", @@ -942,13 +905,6 @@ "authentication-method": "متد اعتبارسنجی", "authentication-type": "نوع اعتبارسنجی", "custom-product-name": "نام سفارشی محصول", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "لایه", "hide-logo": "مخفی سازی نماد", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "زمان پایان ویرایش‌شده به", "a-startAt": "زمان شروع ویرایش‌شده به", "a-receivedAt": "زمان رسیدن ویرایش شده به", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "زمان سررسید فعلی %s د رحال رسیدن است", "pastdue": "زمان سررسید فعلی %s گذشته‌است", "duenow": "زمان سررسید فعلی %s امروز است", @@ -989,7 +943,7 @@ "act-almostdue": "یاآوری سررسید (__timeValue__) از __card__ در حال رسیدن است", "act-pastdue": "یاآوری سررسید (__timeValue__) از __card__ گذشته‌است", "act-duenow": "یاآوری سررسید (__timeValue__) از __card__ هم‌اکنون است", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "به شما در [__board__] __list__/__card__ اشاره‌شده‌است.", "delete-user-confirm-popup": "از حذف این اکانت مطمئن هستید؟ این عملیات برگشت‌ناپذیر است.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "مشاهده همه", "filter-by-unread": "فیلتر با خوانده نشده", "mark-all-as-read": "علامت همه به خوانده شده", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "حذف همه خوانده شده", "allow-rename": "اجازه تغییر نام", "allowRenamePopup-title": "اجازه تغییر نام", @@ -1048,10 +1001,6 @@ "person": "فرد", "my-cards": "کارت های من", "card": "کارت", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "لیست", "board": "برد", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "پنهان کردن همه موارد چک لیست", "support": "پشتیبانی", "supportPopup-title": "پشتیبانی", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "زمان", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "شروع", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "وضعیت", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "تمام شده", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/fa.i18n.json b/imports/i18n/data/fa.i18n.json index b9d761803..38d280360 100644 --- a/imports/i18n/data/fa.i18n.json +++ b/imports/i18n/data/fa.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "%s نظر حذف شد", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "قالب‌ها", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "افزودن ضمیمه", @@ -98,7 +86,6 @@ "add-card": "افزودن کارت", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "افزودن لیست", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "افزودن اعضا", "added": "اضافه گردید", - "addMemberPopup-title": "افزودن اعضا", + "addMemberPopup-title": "اعضا", "memberPopup-title": "تنظیمات اعضا", "admin": "مدیر", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "امکان دیدن و ویرایش کارت‌ها، حذف اعضا و تغییرِ تنظیماتِ برد.", "admin-announcement": "اعلان", "admin-announcement-active": "فعال کردن اعلان‌های سمت سیستم", "admin-announcement-title": "اعلان از سوی مدیر", @@ -167,16 +154,12 @@ "board-background-image-url": "لینک تصویر پس زمینه", "add-background-image": "اضافه کردن تصویر پس زمینه", "remove-background-image": "حذف تصویر پس زمینه", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s ستاره", "board-not-found": "برد پیدا نشد", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "تغییر دسترسی‌ها", "change-settings": "تغییر تنظیمات", "changeAvatarPopup-title": "تغییر آواتار", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "تغییر زبان", "changePasswordPopup-title": "تغییر کلمه عبور", "changePermissionsPopup-title": "تغییر دسترسی‌ها", @@ -335,16 +316,10 @@ "comment-placeholder": "درج نظر", "comment-only": "فقط نظر", "comment-only-desc": "فقط می‌تواند روی کارت‌ها نظر دهد.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "هیچ کامنتی موجود نیست", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "نظرات و فعالیت ها را نمی توان دید.", "worker": "کارگر", "worker-desc": "تنها می‌توانید کارت‌ها را جابجا کنید، آنها را به خود محول کنید و نظر دهید.", "computer": "رایانه", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "درج پیوند کارت در حافظه", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "ارتباط دادن کارت", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "ایجاد", "createBoardPopup-title": "ایجاد برد", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "وارد کردن برد", "createLabelPopup-title": "ایجاد لیبل", "createCustomField": "ایجاد فیلد", @@ -385,7 +358,7 @@ "date": "تاریخ", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "رد", "default-avatar": "آواتار پیش‌فرض", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "شما می توانید لیست را به آرشیو انتقال دهید تا آن را از برد حذف نمایید و فعالیت های خود را حفظ نمایید", "lists": "لیست‌ها", "swimlanes": "مسیرها", - "calendar": "تقویم", - "gantt": "گانت", "log-out": "خروج", "log-in": "ورود", "loginPopup-title": "ورود", "memberMenuPopup-title": "تنظیمات اعضا", - "grey-icons": "Grey Icons", "members": "اعضا", "menu": "منو", "move-selection": "حرکت مورد انتخابی", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "انتقال به پایین", "moveCardToTop-title": "انتقال به بالا", "moveSelectionPopup-title": "حرکت مورد انتخابی", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "امکان چند انتخابی", "multi-selection-label": "تغییر لیبل انتخاب‌شده‌ها", "multi-selection-member": "تغییر عضو برای انتخاب‌شده‌ها", @@ -587,8 +555,6 @@ "no-results": "بدون نتیجه", "normal": "عادی", "normal-desc": "امکان نمایش و تنظیم کارت بدون امکان تغییر تنظیمات", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "دعوت نامه هنوز پذیرفته نشده است", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "اطلاع رسانی از هرگونه تغییر در بردها، لیست‌ها یا کارت‌هایی که از آنها دیده‌بانی می‌کنید", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "اجازه تغییر ایمیل", "accounts-allowUserNameChange": "اجازه تغییر نام کاربری", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "ساخته شده در", "modifiedAt": "تغییر یافته در", "verified": "معتبر", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "تغییر تاریخ رسید", "editCardEndDatePopup-title": "تغییر تاریخ پایان", "setCardColorPopup-title": "انتخاب رنگ", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "انتخاب کردن رنگ", "setSwimlaneColorPopup-title": "انتخاب کردن رنگ", "setListColorPopup-title": "انتخاب کردن رنگ", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "تمام لیست ها، کارت ها، لیبل ها و فعالیت ها حذف خواهند شد و شما نمی توانید محتوای برد را بازیابی کنید. هیچ واکنشی وجود ندارد", "boardDeletePopup-title": "حذف برد؟", "delete-board": "حذف برد", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "زیروظایفِ برد __board__", @@ -942,13 +905,6 @@ "authentication-method": "متد اعتبارسنجی", "authentication-type": "نوع اعتبارسنجی", "custom-product-name": "نام سفارشی محصول", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "لایه", "hide-logo": "مخفی سازی نماد", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "زمان پایان ویرایش‌شده به", "a-startAt": "زمان شروع ویرایش‌شده به", "a-receivedAt": "زمان رسیدن ویرایش شده به", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "زمان سررسید فعلی %s د رحال رسیدن است", "pastdue": "زمان سررسید فعلی %s گذشته‌است", "duenow": "زمان سررسید فعلی %s امروز است", @@ -989,7 +943,7 @@ "act-almostdue": "یاآوری سررسید (__timeValue__) از __card__ در حال رسیدن است", "act-pastdue": "یاآوری سررسید (__timeValue__) از __card__ گذشته‌است", "act-duenow": "یاآوری سررسید (__timeValue__) از __card__ هم‌اکنون است", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "به شما در [__board__] __list__/__card__ اشاره‌شده‌است.", "delete-user-confirm-popup": "از حذف این اکانت مطمئن هستید؟ این عملیات برگشت‌ناپذیر است.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "مشاهده همه", "filter-by-unread": "فیلتر با خوانده نشده", "mark-all-as-read": "علامت همه به خوانده شده", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "حذف همه خوانده شده", "allow-rename": "اجازه تغییر نام", "allowRenamePopup-title": "اجازه تغییر نام", @@ -1048,10 +1001,6 @@ "person": "فرد", "my-cards": "کارت های من", "card": "کارت", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "لیست", "board": "برد", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "زمان", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "شروع", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "وضعیت", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "تمام شده", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/fi.i18n.json b/imports/i18n/data/fi.i18n.json index 13f878b6d..cdec49369 100644 --- a/imports/i18n/data/fi.i18n.json +++ b/imports/i18n/data/fi.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "poisti kommentin %s", "activity-receivedDate": "muokkasi vastaanotettu päiväksi %s / %s", "activity-startDate": "muokkasi aloituspäiväksi %s / %s", - "allboards.starred": "Suosikki", - "allboards.templates": "Mallit", - "allboards.remaining": "Jäljellä", - "allboards.workspaces": "Työtilat", - "allboards.add-workspace": "Lisää työtila", - "allboards.add-workspace-prompt": "Työtilan nimi", - "allboards.add-subworkspace": "Lisää alityötila", - "allboards.add-subworkspace-prompt": "Alityötilan nimi", - "allboards.edit-workspace": "Muokkaa työtilaa", - "allboards.edit-workspace-name": "Työtilan nimi", - "allboards.edit-workspace-icon": "Työtilan ikoni (markdown)", - "multi-selection-active": "Valitse taulut napsauttamalla valintaruutuja", "activity-dueDate": "muokkasi eräpäiväksi %s / %s", "activity-endDate": "muokkasi loppumispäiväksi %s / %s", "add-attachment": "Lisää liite", @@ -98,7 +86,6 @@ "add-card": "Lisää kortti", "add-card-to-top-of-list": "Lisää kortti listan alkuun", "add-card-to-bottom-of-list": "Lisää kortti listan loppuun", - "addListPopup-title": "Lisää lista", "setListWidthPopup-title": "Aseta leveydet", "set-list-width": "Aseta leveydet", "set-list-width-value": "Aseta minimi ja maksimi leveydet (pikseliä)", @@ -122,10 +109,10 @@ "add-after-list": "Lisää listan jälkeen", "add-members": "Lisää jäseniä", "added": "Lisätty", - "addMemberPopup-title": "Lisää jäseniä", + "addMemberPopup-title": "Jäsenet", "memberPopup-title": "Jäsenasetukset", "admin": "Ylläpitäjä", - "admin-desc": "Voi nähdä ja muokata kortteja, poistaa jäseniä, ja muuttaa taulun asetuksia. Voi nähdä taulun toiminnot.", + "admin-desc": "Voi nähdä ja muokata kortteja, poistaa jäseniä, ja muuttaa taulun asetuksia.", "admin-announcement": "Ilmoitus", "admin-announcement-active": "Aktiivinen järjestelmänlaajuinen ilmoitus", "admin-announcement-title": "Ilmoitus ylläpitäjältä", @@ -167,16 +154,12 @@ "board-background-image-url": "Taustakuvan URL", "add-background-image": "Lisää taustakuva", "remove-background-image": "Poista taustakuva", - "show-at-all-boards-page": "Näytä Kaikki taulut sivulla", - "board-info-on-my-boards": "Kaikki taulut asetukset", - "boardInfoOnMyBoardsPopup-title": "Kaikki taulut asetukset", + "show-at-all-boards-page" : "Näytä Kaikki taulut sivulla", + "board-info-on-my-boards" : "Kaikki taulut asetukset", + "boardInfoOnMyBoardsPopup-title" : "Kaikki taulut asetukset", "boardInfoOnMyBoards-title": "Kaikki taulut asetukset", "show-card-counter-per-list": "Näytä korttien määrä per lista", "show-board_members-avatar": "Näytä taulun jäsenien profiilikuvat", - "board_members": "Kaikki taulun jäsenet", - "card_members": "Kakki tämän kortin jäsenet tällä taululla", - "board_assignees": "Kaikki käsittelijät kaikilla korteilla tällä taululla", - "card_assignees": "Kaikki käsittelijät nykyisellä kortilla tällä taululla", "board-nb-stars": "%s tähteä", "board-not-found": "Taulua ei löytynyt", "board-private-info": "Tämä taulu tulee olemaan <strong>yksityinen</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Muokkaa oikeuksia", "change-settings": "Muokkaa asetuksia", "changeAvatarPopup-title": "Muokkaa profiilikuvaa", - "delete-avatar-confirm": "Haluatko varmasti poistaa tämän profiilikuvan?", - "deleteAvatarPopup-title": "Poista profiilikuva?", "changeLanguagePopup-title": "Vaihda kieltä", "changePasswordPopup-title": "Vaihda salasana", "changePermissionsPopup-title": "Muokkaa oikeuksia", @@ -335,16 +316,10 @@ "comment-placeholder": "Kirjoita kommentti", "comment-only": "Vain kommentointi", "comment-only-desc": "Voi vain kommentoida kortteja", - "comment-assigned-only": "Vain käsittelijän kommentti", - "comment-assigned-only-desc": "Vain käsittelijä kortit näkyvissä. Voi vain kommentoida.", "comment-delete": "Haluatko varmasti poistaa kommentin?", "deleteCommentPopup-title": "Poista kommentti?", "no-comments": "Ei kommentteja", - "no-comments-desc": "Ei voi nähdä kommentteja", - "read-only": "Vain luku", - "read-only-desc": "Voi vain katsoa kortteja. Ei voi muokata.", - "read-assigned-only": "Vain käsittelijä lukee", - "read-assigned-only-desc": "Vain käsittelijä kortit näkyvissä. Ei voi muokata.", + "no-comments-desc": "Ei voi nähdä kommentteja ja toimintaa.", "worker": "Työntekijä", "worker-desc": "Voi vain siirtää kortteja, ilmoittautua kortin käsittelijäksi ja kommentoida.", "computer": "Tietokone", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Haluatko varmasti poistaa tarkistuslistan?", "subtaskDeletePopup-title": "Poista alitehtävä?", "checklistDeletePopup-title": "Poista tarkistuslista?", - "checklistItemDeletePopup-title": "Poista tarkistuslistan kohta?", "copy-card-link-to-clipboard": "Kopioi kortin linkki leikepöydälle", "copy-text-to-clipboard": "Kopioi teksti leikepöydälle", "linkCardPopup-title": "Linkitä kortti", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Ensimmäisen kortin otsikko\", \"description\":\"Ensimmäisen kortin kuvaus\"}, {\"title\":\"Toisen kortin otsikko\",\"description\":\"Toisen kortin kuvaus\"},{\"title\":\"Viimeisen kortin otsikko\",\"description\":\"Viimeisen kortin kuvaus\"} ]", "create": "Luo", "createBoardPopup-title": "Luo taulu", - "createTemplateContainerPopup-title": "Lisää mallikontti", "chooseBoardSourcePopup-title": "Tuo taulu", "createLabelPopup-title": "Luo nimilappu", "createCustomField": "Luo kenttä", @@ -385,7 +358,7 @@ "date": "Päivämäärä", "date-format": "Päivämäärämuoto", "date-format-yyyy-mm-dd": "VVVV-KK-PP", - "date-format-dd-mm-yyyy": "PP-KK-VVVV", + "date-format-dd-mm-yyyy": "PP-KK-VVVV", "date-format-mm-dd-yyyy": "KK-PP-VVVV", "decline": "Kieltäydy", "default-avatar": "Oletusprofiilikuva", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Voit siirtää listan Arkistoon poistaaksesi sen taululta ja säilyttääksesi toimintalokin.", "lists": "Listat", "swimlanes": "Uimaradat", - "calendar": "Kalenteri", - "gantt": "Gantt", "log-out": "Kirjaudu ulos", "log-in": "Kirjaudu sisään", "loginPopup-title": "Kirjaudu sisään", "memberMenuPopup-title": "Jäsenasetukset", - "grey-icons": "Harmaat ikonit", "members": "Jäsenet", "menu": "Valikko", "move-selection": "Siirrä valinta", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Siirrä alimmaiseksi", "moveCardToTop-title": "Siirrä ylimmäiseksi", "moveSelectionPopup-title": "Siirrä valinta", - "copySelectionPopup-title": "Kopioi valinta", - "selection-color": "Valinnan väri", "multi-selection": "Monivalinta", "multi-selection-label": "Aseta nimilappu valinnalle", "multi-selection-member": "Aseta jäsen valinnalle", @@ -587,8 +555,6 @@ "no-results": "Ei tuloksia", "normal": "Normaali", "normal-desc": "Voi nähdä ja muokata kortteja. Ei voi muokata asetuksia.", - "normal-assigned-only": "Vain käsittelijä normaali", - "normal-assigned-only-desc": "Vain käsittelijä kortit näkyvissä. Muokkaa normaalina käyttäjänä.", "not-accepted-yet": "Kutsua ei ole hyväksytty vielä", "notify-participate": "Vastaanota päivityksiä kaikilta korteilta jotka olet tehnyt tai joissa olet luoja tai jäsen", "notify-watch": "Vastaanota päivityksiä kaikilta tauluilta, listoilta tai korteilta joita seuraat.", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Salli sähköpostiosoitteen muuttaminen", "accounts-allowUserNameChange": "Salli käyttäjätunnuksen muuttaminen", "tableVisibilityMode-allowPrivateOnly": "Taulujen näkyvyys: Salli vain yksityiset taulut", - "tableVisibilityMode": "Taulujen näkyvyys", + "tableVisibilityMode" : "Taulujen näkyvyys", "createdAt": "Luotu", "modifiedAt": "Muokattu", "verified": "Varmistettu", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Vaihda vastaanottamispäivää", "editCardEndDatePopup-title": "Vaihda loppumispäivää", "setCardColorPopup-title": "Aseta väri", - "setSelectionColorPopup-title": "Aseta valinnan väri", "setCardActionsColorPopup-title": "Valitse väri", "setSwimlaneColorPopup-title": "Valitse väri", "setListColorPopup-title": "Valitse väri", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Kaikki listat, kortit, nimilaput ja toimet poistetaan ja et pysty palauttamaan taulun sisältöä. Tätä ei voi peruuttaa.", "boardDeletePopup-title": "Poista taulu?", "delete-board": "Poista taulu", - "delete-all-notifications": "Poista kaikki ilmoitukset", - "delete-all-notifications-confirm": "Oletko varma että haluat poistaa kaikki ilmoitukset? Tätä toimintoa ei voi perua.", "delete-duplicate-lists": "Poista ylimääräiset lista kopiot", "delete-duplicate-lists-confirm": "Oletko varma? Tämä poistaa kaikki ylimääräiset lista kopiot, joilla on sama nimi ja jotka eivät sisällä kortteja.", "default-subtasks-board": "Alitehtävät taululle __board__", @@ -942,13 +905,6 @@ "authentication-method": "Kirjautumistapa", "authentication-type": "Kirjautumistyyppi", "custom-product-name": "Mukautettu tuotenimi", - "custom-head-tags-enabled": "Ota käytöön mukautetut head tagit", - "custom-head-meta-tags": "Mukautetut meta tagit (HTML)", - "custom-head-link-tags": "Mukautetut link tagit (HTML)", - "custom-manifest-enabled": "Ota käyttöön mukautettu web manifest", - "custom-head-manifest-content": "Mukautettu web manifest sisältö (JSON)", - "custom-assetlinks-enabled": "Ota käyttöön mukautettu assetlinks.json", - "custom-assetlinks-content": "Mukautettu assetlinks.json sisältö (JSON)", "layout": "Ulkoasu", "hide-logo": "Piilota Logo", "hide-card-counter-list": "Piilota korttilaskuri lista kaikilla tauluilla", @@ -979,8 +935,6 @@ "a-endAt": "muokattu loppumisajaksi", "a-startAt": "muokattu aloitusajaksi", "a-receivedAt": "muokattu vastaanottamisajaksi", - "above-selected-card": "Valitun kortin yläpuolelle", - "below-selected-card": "Valitun kortin alapuolelle", "almostdue": "nykyinen eräaika %s lähestyy", "pastdue": "nykyinen eräaika %s on mennyt", "duenow": "nykyinen eräaika %s on tänään", @@ -989,7 +943,7 @@ "act-almostdue": "muistutti nykyisen eräajan (__timeValue__) kortilla __card__ lähestyvän", "act-pastdue": "muistutti nykyisen eräajan (__timeValue__) kortilla __card__ menneen", "act-duenow": "muistutti nykyisen eräajan (__timeValue__) kortilla __card__ olevan nyt", - "act-atUserComment": "mainitsi sinut kortilla __card__: __comment__ listalla __list__ uimaradalla __swimlane__ taululla __board__", + "act-atUserComment": "Sinut mainittiin [__board__] __list__/__card__", "delete-user-confirm-popup": "Haluatko varmasti poistaa tämän käyttäjätilin? Tätä ei voi peruuttaa.", "delete-team-confirm-popup": "Haluatko varmasti poistaa tämän tiimin? Tätä ei voi peruuttaa.", "delete-org-confirm-popup": "Haluatko varmasti poistaa tämän organisaation? Tätä ei voi peruuttaa.", @@ -1013,7 +967,6 @@ "view-all": "Näytä kaikki", "filter-by-unread": "Suodata lukemattomat", "mark-all-as-read": "Merkkaa kaikki luetuksi", - "mark-all-as-unread": "Merkkaa kaikki lukemattomaksi", "remove-all-read": "Poista kaikki luetut", "allow-rename": "Salli uudelleennimeäminen", "allowRenamePopup-title": "Salli uudelleennimeäminen", @@ -1048,10 +1001,6 @@ "person": "Henkilö", "my-cards": "Korttini", "card": "Kortti", - "today": "Tänään", - "day": "Päivä", - "week": "Viikko", - "month": "Kuukausi", "list": "Lista", "board": "Taulu", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Piilota kaikki tarkistuslistan kohdat", "support": "Tuki", "supportPopup-title": "Tuki", - "support-page-enabled": "Tuki sivu käytössä", - "support-info-not-added-yet": "Tuki tietoja ei ole vielä lisätty", - "support-info-only-for-logged-in-users": "Tuki tiedot on vain kirjautuneille käyttäjille.", - "support-title": "Tuki otsikko", - "support-content": "Tuki sisältö", "accessibility": "Saavutettavuus", "accessibility-page-enabled": "Saavutettavuus sivu käytössä", "accessibility-info-not-added-yet": "Saavutettavuus tietoja ei ole lisätty vielä", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Ajastettu työ poistettu onnistuneesti", "cron-job-pause-failed": "Ajastetun työn keskeyttäminen epäonnistui", "cron-job-paused": "Ajastettu työ keskeytetty onnistuneesti", - "cron-job-resume-failed": "Ajastetun työn jatkaminen epäonnistui", - "cron-job-resumed": "Aikataulu työtä jatkettu onnistuneesti", - "cron-job-start-failed": "Aikataulu työn käynnistäminen epäonnistui", - "cron-job-started": "Aikataulu työ aloitettu onnistuneesti", - "cron-migration-errors": "Siirto virheet", - "cron-migration-warnings": "Siirto varoitukset", - "cron-no-errors": "Ei virheitä joita näyttää", - "cron-error-severity": "Vakavuus", - "cron-error-time": "Aika", - "cron-error-message": "Virhe viesti", - "cron-error-details": "Yksityiskohdat", - "cron-clear-errors": "Tyhjennä kaikki virheet", - "cron-retry-failed": "Yritä uudelleen epäonnistuneita siirtoja", - "cron-resume-paused": "Jatka keskeytettyjä siirtoja", - "cron-errors-cleared": "Kaikki virheet tyhjennetty onnistuneesti", - "cron-no-failed-migrations": "Ei epäonnistuneita siirtoja joita yrittää uudelleen", - "cron-no-paused-migrations": "Ei keskeytettyjä siirtoja joita jatkaa", - "cron-migrations-resumed": "Siirrot jatkuu onnistuneesti", - "cron-migrations-retried": "Epäonnistuneet siirrot jatkuu onnistuneesti", - "complete": "Valmis", - "idle": "Tyhjäkäynti", "filesystem-path-description": "Peruspolku tiedostojen tallennukseen", "gridfs-enabled": "GridFS käytössä", "gridfs-enabled-description": "Käytä MongoDB GridFS:ää tiedostojen tallennukseen", - "all-migrations": "Kaikki siirrot", - "select-migration": "Valitse siirto", - "start": "Alkaa", - "pause": "Tauko", - "stop": "Pysäytä", - "migration-starting": "Aloita siirrot...", - "migration-pausing": "Keskeytetään siirrot...", - "migration-stopping": "Pysäytetään siirrot...", "migration-pause-failed": "Siirtojen keskeyttäminen epäonnistui", "migration-paused": "Siirrot keskeytetty onnistuneesti", "migration-progress": "Siirtojen edistyminen", "migration-start-failed": "Siirtojen aloittaminen epäonnistui", "migration-started": "Siirrot aloitettu onnistuneesti", - "migration-not-needed": "Ei tarvitse siirtää mitään", "migration-status": "Siirron tila", "migration-stop-confirm": "Haluatko varmasti pysäyttää kaikki siirrot?", "migration-stop-failed": "Siirtojen pysäyttäminen epäonnistui", @@ -1490,73 +1404,7 @@ "back-to-settings": "Takaisin asetuksiin", "board-id": "Taulun tunnus", "board-migration": "Taulun siirto", - "board-migrations": "Taulu siirrot", "card-show-lists-on-minicard": "Näytä listat minikortilla", - "comprehensive-board-migration": "Perusteellinen taulu siirto", - "comprehensive-board-migration-description": "Suorittaa kattavia tarkistuksia ja korjauksia taulun tietojen eheyden varmistamiseksi, mukaan lukien listajärjestyksen, korttien sijainnit ja uimaratarakenteen.", - "delete-duplicate-empty-lists-migration": "Poista kaksoiskappaleet tyhjistä listoista", - "delete-duplicate-empty-lists-migration-description": "Poistaa tyhjät kaksoiskappalelistat turvallisesti. Poistaa vain listat, joissa ei ole kortteja JA joilla on toinen samanniminen lista, joka sisältää kortteja.", - "lost-cards": "Kadonneet kortit", - "lost-cards-list": "Palautetut kohteet", - "restore-lost-cards-migration": "Palauta kadonneet kortit", - "restore-lost-cards-migration-description": "Etsii ja palauttaa kortit ja listat, joista puuttuu swimlaneId tai listId. Luo 'Kadonneet kortit' -uimaradan, jotta kaikki kadonneet ovat taas näkyvissä.", - "restore-all-archived-migration": "Palauta kaikki arkistoidut", - "restore-all-archived-migration-description": "Palauttaa kaikki arkistoidut uimaradat, listat ja kortit. Korjaa automaattisesti puuttuvat uimaratatunnukset tai listatunnukset, jotta kohteet ovat näkyvissä.", - "fix-missing-lists-migration": "Korjaa puuttuvat listat", - "fix-missing-lists-migration-description": "Havaitsee ja korjaa puuttuvat tai vioittuneet listat taulun rakenteessa.", - "fix-avatar-urls-migration": "Korjaa avatar-URL-osoitteet", - "fix-avatar-urls-migration-description": "Päivittää taulun jäsenten avatar-osoitteiden URL-osoitteet oikean tallennustilan käyttämiseksi ja korjaa rikkinäiset avatar-viittaukset.", - "fix-all-file-urls-migration": "Korjaa kaikki tiedostojen URL-osoitteet", - "fix-all-file-urls-migration-description": "Päivittää kaikkien tällä taululla olevien tiedostoliitteiden URL-osoitteet käyttämään oikeaa tallennuspalvelinta ja korjaa rikkinäiset tiedostoviittaukset.", - "migration-needed": "Siirto tarvitaan", - "migration-complete": "Valmis", - "migration-running": "Suoritetaan...", - "migration-successful": "Siirto valmistui onnistuneesti", - "migration-failed": "Siirto epäonnistui", - "migrations": "Siirrot", - "migrations-admin-only": "Vain taulu ylläpitäjät voivat suorittaa siirtoja", - "migrations-description": "Suorita tietojen eheystarkistukset ja korjaukset tälle taululle. Jokainen siirto voidaan suorittaa erikseen.", - "no-issues-found": "Ei löytynyt ongelmia", - "run-migration": "Suorita siirto", - "run-comprehensive-migration-confirm": "Tämä suorittaa kattavan siirron, jolla tarkistetaan ja korjataan taulun tietojen eheys. Tämä voi kestää hetken. Jatketaanko?", - "run-delete-duplicate-empty-lists-migration-confirm": "Tämä muuntaa ensin kaikki jaetut listat uimaratakohtaisiksi listoiksi ja poistaa sitten tyhjät listat, joissa on samanniminen kaksoiskappale, joka sisältää kortteja. Vain todella tarpeettomat tyhjät listat poistetaan. Jatketaanko?", - "run-restore-lost-cards-migration-confirm": "Tämä luo Kadonneet kortit -uimaradan ja palauttaa kaikki kortit ja listat, joista puuttuu uimaradan tunnus tai listan tunnus. Tämä vaikuttaa vain arkistoimattomiin kohteisiin. Jatketaanko?", - "run-restore-all-archived-migration-confirm": "Tämä palauttaa KAIKKI arkistoidut uintikaistat, listat ja kortit, jolloin ne näkyvät taas. Puuttuvista tunnuksista puuttuvat kohteet korjataan automaattisesti. Tätä ei voi helposti perua. Jatketaanko?", - "run-fix-missing-lists-migration-confirm": "Tämä havaitsee ja korjaa puuttuvat tai vioittuneet listat taulun rakenteessa. Jatketaanko?", - "run-fix-avatar-urls-migration-confirm": "Tämä päivittää taulun jäsenten avatar-URL-osoitteet käyttämään oikeaa tallennustilaa. Jatketaanko?", - "run-fix-all-file-urls-migration-confirm": "Tämä päivittää kaikkien tällä taululla olevien tiedostoliitteiden URL-osoitteet käyttämään oikeaa tallennuspalvelinta. Jatketaanko?", - "restore-lost-cards-nothing-to-restore": "Ei kadonneita uintikaistoja, listoja tai kortteja palautettavaksi", - - "migration-progress-title": "Taulu siirto meneillään", - "migration-progress-overall": "Kokonaisedistyminen", - "migration-progress-current-step": "Nykyinen vaihe", - "migration-progress-status": "Tilanne", - "migration-progress-details": "Yksityiskohdat", - "migration-progress-note": "Odota hetki, siirrämme taulusi uusimpaan rakenteeseen...", - "steps": "askelta", - "view": "Näkymä", - "has-swimlanes": "Sisältää uimaratoja", - - "step-analyze-board-structure": "Analysoi taulun rakennetta", - "step-fix-orphaned-cards": "Korjaa orvot kortit", - "step-convert-shared-lists": "Muunna jaetut listat", - "step-ensure-per-swimlane-lists": "Varmista uimaratakohtaiset listat", - "step-validate-migration": "Varmistetaan siirto", - "step-fix-avatar-urls": "Korjaa avatar-URL-osoitteet", - "step-fix-attachment-urls": "Korjaa liitetiedosto URLit", - "step-analyze-lists": "Analysoidaan listoja", - "step-create-missing-lists": "Luo puuttuvat listat", - "step-update-cards": "Päivitä kortit", - "step-finalize": "Viimeistellään", - "step-delete-duplicate-empty-lists": "Poista kaksoiskappaleet tyhjistä listoista", - "step-ensure-lost-cards-swimlane": "Varmistetaan hävinneiden korttien uimarata", - "step-restore-lists": "Palauta listat", - "step-restore-cards": "Palauta kortit", - "step-restore-swimlanes": "Palauta uimaradat", - "step-fix-missing-ids": "Korjaa puuttuvat ID:t", - "step-scan-users": "Tarkistetaan taulun jäsenten avatarit", - "step-scan-files": "Tarkistetaan taulun liitetiedostot", - "step-fix-file-urls": "Korjataan tiedosto URLit", "cleanup": "Siivous", "cleanup-old-jobs": "Siivoa vanhat työt", "completed": "Valmistunut", @@ -1637,7 +1485,6 @@ "schedule": "Aikataulu", "search-boards-or-operations": "Etsi tauluja tai toimintoja...", "show-list-on-minicard": "Näytä lista minikortilla", - "showChecklistAtMinicard": "Näytä tarkistuslista minikortilla", "showing": "Näytetään", "start-test-operation": "Aloita testitoiminto", "start-time": "Aloitusaika", @@ -1650,37 +1497,7 @@ "total-size": "Koko yhteensä", "unmigrated-boards": "Siirtämättömät taulut", "weight": "Paino", - "cron": "Ajastus", - "current-step": "Nykyinen vaihe", - "otp": "OTP-koodi", - "create-account": "Luo tili", - "already-account": "Onko sinulla jo tili? Kirjaudu sisään", - "available-repositories": "Käytettävissä olevat repot", - "repositories": "Repot", - "repository": "Repo", - "repository-name": "Repo nimi", - "size-bytes": "Koko (tavua)", - "last-modified": "Viimeksi muokattu", - "no-repositories": "Repoja ei löytynyt", - "create-repository": "Luo repo", - "upload-repository": "Uppaa/Päivitä repo", - "api-endpoints": "API päätepisteet", - "sign-in-to-upload": "Kirjaudu sisään upataksesi repoja", - "account-locked": "Tili on tilapäisesti lukittu liian monen epäonnistuneen kirjautumisyrityksen vuoksi. Yritä myöhemmin uudelleen.", - "otp-required": "OTP-koodi vaaditaan", - "invalid-credentials": "Virheellinen käyttäjätunnus tai salasana", - "username-password-required": "Käyttäjätunnus ja salasana vaaditaan", - "password-mismatch": "Salasanat eivät täsmää", - "username-too-short": "Käyttäjätunnuksen on oltava vähintään 3 merkkiä pitkä", - "user-exists": "Käyttäjä on jo olemassa", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Tilin luominen epäonnistui", - "login": "Kirjaudu sisään", - "confirm": "Varmista", - "error": "Virhe", - "file": "Tiedosto", - "log": "Loki", - "logout": "Kirjaudu ulos", - "server": "Palvelin", - "protocol": "Protokolla" + "idle": "Tyhjäkäynti", + "complete": "Valmis", + "cron": "Ajastus" } diff --git a/imports/i18n/data/fr-CH.i18n.json b/imports/i18n/data/fr-CH.i18n.json index 37eb511e4..ab5df8545 100644 --- a/imports/i18n/data/fr-CH.i18n.json +++ b/imports/i18n/data/fr-CH.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/fr-FR.i18n.json b/imports/i18n/data/fr-FR.i18n.json index 110f3cce6..643f299b7 100644 --- a/imports/i18n/data/fr-FR.i18n.json +++ b/imports/i18n/data/fr-FR.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "commentaire supprimé %s", "activity-receivedDate": "date de réception éditée de %s à %s", "activity-startDate": "date de début éditée de %s à %s", - "allboards.starred": "Starred", - "allboards.templates": "Modèles", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "date d'échéance éditée de %s à %s", "activity-endDate": "date de fin éditée de %s à %s", "add-attachment": "Ajouter une pièce jointe", @@ -98,7 +86,6 @@ "add-card": "Ajouter une carte", "add-card-to-top-of-list": "Ajouter la carte en haut de la liste", "add-card-to-bottom-of-list": "Ajouter la carte en bas de la liste", - "addListPopup-title": "Ajouter une liste", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Ajouter après la liste", "add-members": "Assigner des participants", "added": "Ajouté le", - "addMemberPopup-title": "Assigner des participants", + "addMemberPopup-title": "Participants", "memberPopup-title": "Préférence du participant", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Peut voir et éditer les cartes, supprimer des participants et changer les paramètres du tableau.", "admin-announcement": "Annonce", "admin-announcement-active": "Annonce destinée à tous", "admin-announcement-title": "Annonce de l'administrateur", @@ -167,16 +154,12 @@ "board-background-image-url": "URL de l'image de fond", "add-background-image": "Ajouter une image de fond", "remove-background-image": "Supprimer l'image de fond", - "show-at-all-boards-page": "Afficher sur la page de tous les tableaux", - "board-info-on-my-boards": "Paramètres de tous les tableaux", - "boardInfoOnMyBoardsPopup-title": "Paramètres de tous les tableaux", + "show-at-all-boards-page" : "Afficher sur la page de tous les tableaux", + "board-info-on-my-boards" : "Paramètres de tous les tableaux", + "boardInfoOnMyBoardsPopup-title" : "Paramètres de tous les tableaux", "boardInfoOnMyBoards-title": "Paramètres de tous les tableaux", "show-card-counter-per-list": "Afficher le nombre de cartes par liste", "show-board_members-avatar": "Afficher les avatars des participants du tableau.", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s étoiles", "board-not-found": "Tableau non trouvé", "board-private-info": "Ce tableau sera <strong>privé</strong>", @@ -286,8 +269,6 @@ "change-permissions": "Modifier les permissions", "change-settings": "Modifier les paramètres", "changeAvatarPopup-title": "Modifier l'avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Modifier la langue", "changePasswordPopup-title": "Modifier le mot de passe", "changePermissionsPopup-title": "Modifier les permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Écrire un commentaire", "comment-only": "Commentaire uniquement", "comment-only-desc": "Ne peut que commenter des cartes.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Êtes-vous sûr de vouloir supprimer le commentaire ?", "deleteCommentPopup-title": "Supprimer le commentaire ?", "no-comments": "Aucun commentaire", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Ne peut pas voir les commentaires et les activités.", "worker": "Travailleur", "worker-desc": "Peut seulement déplacer des cartes, s'assigner à une carte et la commenter.", "computer": "Ordinateur", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Êtes-vous sûr de vouloir supprimer la checklist ?", "subtaskDeletePopup-title": "Supprimer la sous-tâche ?", "checklistDeletePopup-title": "Supprimer la checklist ?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copier le lien de la carte dans le presse-papier", "copy-text-to-clipboard": "Copier le texte dans le presse-papier", "linkCardPopup-title": "Lier une Carte", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Titre de la première carte\", \"description\":\"Description de la première carte\"}, {\"title\":\"Titre de la seconde carte\",\"description\":\"Description de la seconde carte\"},{\"title\":\"Titre de la dernière carte\",\"description\":\"Description de la dernière carte\"} ]", "create": "Créer", "createBoardPopup-title": "Créer un tableau", - "createTemplateContainerPopup-title": "Ajouter un conteneur de modèles", "chooseBoardSourcePopup-title": "Importer un tableau", "createLabelPopup-title": "Créer une étiquette", "createCustomField": "Créer un champ personnalisé", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Refuser", "default-avatar": "Avatar par défaut", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Vous pouvez archiver une liste pour l'enlever du tableau tout en conservant son activité.", "lists": "Listes", "swimlanes": "Couloirs", - "calendar": "Calendrier", - "gantt": "Gantt", "log-out": "Déconnexion", "log-in": "Connexion", "loginPopup-title": "Connexion", "memberMenuPopup-title": "Préférence du participant", - "grey-icons": "Grey Icons", "members": "Participants", "menu": "Menu", "move-selection": "Déplacer la sélection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Déplacer tout en bas", "moveCardToTop-title": "Déplacer tout en haut", "moveSelectionPopup-title": "Déplacer la sélection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Sélection multiple", "multi-selection-label": "Définir l'étiquette pour la sélection", "multi-selection-member": "Définir le participant pour la sélection", @@ -587,8 +555,6 @@ "no-results": "Pas de résultats", "normal": "Normal", "normal-desc": "Peut voir et modifier les cartes. Ne peut pas changer les paramètres.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "L'invitation n'a pas encore été acceptée", "notify-participate": "Recevoir les mises à jour de toutes les cartes auxquelles vous participez en tant que créateur ou que participant", "notify-watch": "Recevoir les mises à jour de tous les tableaux, listes ou cartes que vous suivez", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Autoriser le changement d'adresse mail", "accounts-allowUserNameChange": "Autoriser le changement d'identifiant", "tableVisibilityMode-allowPrivateOnly": "Visibilité des tableaux: N'autoriser que des tableaux privés", - "tableVisibilityMode": "Visibilité des tableaux", + "tableVisibilityMode" : "Visibilité des tableaux", "createdAt": "Créé le", "modifiedAt": "Modifié le", "verified": "Vérifié", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Modifier la date de réception", "editCardEndDatePopup-title": "Modifier la date de fin", "setCardColorPopup-title": "Définir la couleur", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choisissez une couleur", "setSwimlaneColorPopup-title": "Choisissez une couleur", "setListColorPopup-title": "Choisissez une couleur", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Toutes les listes, cartes, étiquettes et activités seront supprimées et vous ne pourrez pas retrouver le contenu du tableau. Cela est irréversible.", "boardDeletePopup-title": "Supprimer le tableau ?", "delete-board": "Supprimer le tableau", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Sous-tâches du tableau __board__", @@ -942,13 +905,6 @@ "authentication-method": "Méthode d'authentification", "authentication-type": "Type d'authentification", "custom-product-name": "Nom personnalisé", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Interface", "hide-logo": "Cacher le logo", "hide-card-counter-list": "Cacher la liste des compteurs sur tous les tableaux", @@ -979,8 +935,6 @@ "a-endAt": "Date de fin modifiée à", "a-startAt": "Date de début modifiée à", "a-receivedAt": "Date de réception modifiée à", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "La date d'échéance %s approche", "pastdue": "La date d'échéance %s est passée", "duenow": "La date d'échéance %s est aujourd'hui", @@ -989,7 +943,7 @@ "act-almostdue": "rappelle que l'échéance (__timeValue__) de __card__ approche", "act-pastdue": "rappelle que l'échéance (__timeValue__) de __card__ est passée", "act-duenow": "rappelle que l'échéance (__timeValue__) de __card__ est maintenant", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Vous avez été mentionné dans [__board__] __list__/__card__", "delete-user-confirm-popup": "Êtes-vous sûr de vouloir supprimer ce compte ? Cela est irréversible.", "delete-team-confirm-popup": "Êtes-vous sûr de vouloir supprimer cette équipe ? Cela est irréversible.", "delete-org-confirm-popup": "Êtes-vous sûr de vouloir supprimer cette organisation ? Cela est irréversible.", @@ -1013,7 +967,6 @@ "view-all": "Voir tout", "filter-by-unread": "Filtrer par non lu", "mark-all-as-read": "Marquer comme lus", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Supprimer les lus", "allow-rename": "Autoriser le renommage", "allowRenamePopup-title": "Autoriser le renommage", @@ -1048,10 +1001,6 @@ "person": "Personne", "my-cards": "Mes Cartes", "card": "Carte", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Liste", "board": "Tableau", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Temps", - "cron-error-message": "Error Message", - "cron-error-details": "Détails", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Début", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Statut", - "migration-progress-details": "Détails", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Terminé", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirmer", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/fr.i18n.json b/imports/i18n/data/fr.i18n.json index 89264c9c5..042abb941 100644 --- a/imports/i18n/data/fr.i18n.json +++ b/imports/i18n/data/fr.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "commentaire supprimé %s", "activity-receivedDate": "date de réception éditée de %s à %s", "activity-startDate": "date de début éditée de %s à %s", - "allboards.starred": "Favoris", - "allboards.templates": "Modèles", - "allboards.remaining": "Restant", - "allboards.workspaces": "Espaces de travail", - "allboards.add-workspace": "Ajouter un espace de travail", - "allboards.add-workspace-prompt": "Nom de l'espace de travail", - "allboards.add-subworkspace": "Ajouter un sous-espace de travail", - "allboards.add-subworkspace-prompt": "Nom du sous-espace de travail", - "allboards.edit-workspace": "Modifier l'espace de travail", - "allboards.edit-workspace-name": "Nom de l'espace de travail", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Cliquez sur les cases à cocher pour sélectionner les tableaux", "activity-dueDate": "date d'échéance éditée de %s à %s", "activity-endDate": "date de fin éditée de %s à %s", "add-attachment": "Ajouter une pièce jointe", @@ -98,7 +86,6 @@ "add-card": "Ajouter une carte", "add-card-to-top-of-list": "Ajouter la carte en haut de la liste", "add-card-to-bottom-of-list": "Ajouter la carte en bas de la liste", - "addListPopup-title": "Ajouter une liste", "setListWidthPopup-title": "Définir les largeurs", "set-list-width": "Définir les largeurs", "set-list-width-value": "Définir les largeurs mini. & maxi. (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Ajouter après la liste", "add-members": "Ajouter des participants", "added": "Ajouté le", - "addMemberPopup-title": "Ajouter des participants", + "addMemberPopup-title": "Participants", "memberPopup-title": "Préférence du participant", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Peut voir et éditer les cartes, supprimer des participants et changer les paramètres du tableau.", "admin-announcement": "Annonce", "admin-announcement-active": "Annonce destinée à tous", "admin-announcement-title": "Annonce de l'administrateur", @@ -167,16 +154,12 @@ "board-background-image-url": "URL de l'image de fond", "add-background-image": "Ajouter une image de fond", "remove-background-image": "Supprimer l'image de fond", - "show-at-all-boards-page": "Afficher sur la page de tous les tableaux", - "board-info-on-my-boards": "Paramètres de tous les tableaux", - "boardInfoOnMyBoardsPopup-title": "Paramètres de tous les tableaux", + "show-at-all-boards-page" : "Afficher sur la page de tous les tableaux", + "board-info-on-my-boards" : "Paramètres de tous les tableaux", + "boardInfoOnMyBoardsPopup-title" : "Paramètres de tous les tableaux", "boardInfoOnMyBoards-title": "Paramètres de tous les tableaux", "show-card-counter-per-list": "Afficher le nombre de cartes par liste", "show-board_members-avatar": "Afficher les avatars des participants du tableau.", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s étoiles", "board-not-found": "Tableau non trouvé", "board-private-info": "Ce tableau sera <strong>privé</strong>", @@ -286,8 +269,6 @@ "change-permissions": "Modifier les permissions", "change-settings": "Modifier les paramètres", "changeAvatarPopup-title": "Modifier l'avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Modifier la langue", "changePasswordPopup-title": "Modifier le mot de passe", "changePermissionsPopup-title": "Modifier les permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Écrire un commentaire", "comment-only": "Commentaire uniquement", "comment-only-desc": "Ne peut que commenter des cartes.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Êtes-vous sûr de vouloir supprimer le commentaire ?", "deleteCommentPopup-title": "Supprimer le commentaire ?", "no-comments": "Aucun commentaire", - "no-comments-desc": "Can not see comments.", - "read-only": "Lecture seule", - "read-only-desc": "Peut seulement voir les cartes. Ne peut pas éditer.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Ne peut pas voir les commentaires et les activités.", "worker": "Travailleur", "worker-desc": "Peut seulement déplacer des cartes, s'attribuer une carte et la commenter.", "computer": "Ordinateur", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Êtes-vous sûr de vouloir supprimer la check-list ?", "subtaskDeletePopup-title": "Supprimer la sous-tâche ?", "checklistDeletePopup-title": "Supprimer la check-list ?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copier le lien de la carte dans le presse-papier", "copy-text-to-clipboard": "Copier le texte dans le presse-papier", "linkCardPopup-title": "Lier une Carte", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Titre de la première carte\", \"description\":\"Description de la première carte\"}, {\"title\":\"Titre de la seconde carte\",\"description\":\"Description de la seconde carte\"},{\"title\":\"Titre de la dernière carte\",\"description\":\"Description de la dernière carte\"} ]", "create": "Créer", "createBoardPopup-title": "Créer un tableau", - "createTemplateContainerPopup-title": "Ajouter un conteneur de modèles", "chooseBoardSourcePopup-title": "Importer un tableau", "createLabelPopup-title": "Créer une étiquette", "createCustomField": "Créer un champ personnalisé", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Format de la date", "date-format-yyyy-mm-dd": "AAAA-MM-JJ", - "date-format-dd-mm-yyyy": "JJ-MM-AAAA", + "date-format-dd-mm-yyyy": "JJ-MM-AAAA", "date-format-mm-dd-yyyy": "MM-JJ-AAAA", "decline": "Refuser", "default-avatar": "Avatar par défaut", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Vous pouvez archiver une liste pour l'enlever du tableau tout en conservant son activité.", "lists": "Listes", "swimlanes": "Couloirs", - "calendar": "Calendrier", - "gantt": "Gantt", "log-out": "Déconnexion", "log-in": "Connexion", "loginPopup-title": "Connexion", "memberMenuPopup-title": "Préférence du participant", - "grey-icons": "Grey Icons", "members": "Participants", "menu": "Menu", "move-selection": "Déplacer la sélection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Déplacer tout en bas", "moveCardToTop-title": "Déplacer tout en haut", "moveSelectionPopup-title": "Déplacer la sélection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Sélection multiple", "multi-selection-label": "Définir l'étiquette pour la sélection", "multi-selection-member": "Définir le participant pour la sélection", @@ -587,8 +555,6 @@ "no-results": "Pas de résultats", "normal": "Normal", "normal-desc": "Peut voir et modifier les cartes. Ne peut pas changer les paramètres.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "L'invitation n'a pas encore été acceptée", "notify-participate": "Recevoir les mises à jour de toutes les cartes auxquelles vous participez en tant que créateur ou que participant", "notify-watch": "Recevoir les mises à jour de tous les tableaux, listes ou cartes que vous suivez", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Autoriser le changement d'adresse de courriel", "accounts-allowUserNameChange": "Autoriser le changement d'identifiant", "tableVisibilityMode-allowPrivateOnly": "Visibilité des tableaux: N'autoriser que des tableaux privés", - "tableVisibilityMode": "Visibilité des tableaux", + "tableVisibilityMode" : "Visibilité des tableaux", "createdAt": "Créé le", "modifiedAt": "Modifié le", "verified": "Vérifié", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Modifier la date de réception", "editCardEndDatePopup-title": "Modifier la date de fin", "setCardColorPopup-title": "Définir la couleur", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choisissez une couleur", "setSwimlaneColorPopup-title": "Choisissez une couleur", "setListColorPopup-title": "Choisissez une couleur", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Toutes les listes, cartes, étiquettes et activités seront supprimées et vous ne pourrez pas retrouver le contenu du tableau. Cela est irréversible.", "boardDeletePopup-title": "Supprimer le tableau ?", "delete-board": "Supprimer le tableau", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Supprimer les listes en doublon ? ", "delete-duplicate-lists-confirm": "Êtes-vous sûr ? Cela supprimera toutes les listes en doublon qui ont le même nom et qui ne contiennent aucune carte.", "default-subtasks-board": "Sous-tâches du tableau __board__", @@ -942,13 +905,6 @@ "authentication-method": "Méthode d'authentification", "authentication-type": "Type d'authentification", "custom-product-name": "Nom personnalisé", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Interface", "hide-logo": "Cacher le logo", "hide-card-counter-list": "Cacher la liste des compteurs sur tous les tableaux", @@ -979,8 +935,6 @@ "a-endAt": "Date de fin modifiée à", "a-startAt": "Date de début modifiée à", "a-receivedAt": "Date de réception modifiée à", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "La date d'échéance %s approche", "pastdue": "La date d'échéance %s est passée", "duenow": "La date d'échéance %s est aujourd'hui", @@ -989,7 +943,7 @@ "act-almostdue": "rappelle que l'échéance (__timeValue__) de __card__ approche", "act-pastdue": "rappelle que l'échéance (__timeValue__) de __card__ est passée", "act-duenow": "rappelle que l'échéance (__timeValue__) de __card__ est maintenant", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Vous avez été mentionné dans [__board__] __list__/__card__", "delete-user-confirm-popup": "Êtes-vous sûr de vouloir supprimer ce compte ? Cela est irréversible.", "delete-team-confirm-popup": "Êtes-vous sûr de vouloir supprimer cette équipe ? Cela est irréversible.", "delete-org-confirm-popup": "Êtes-vous sûr de vouloir supprimer cette organisation ? Cela est irréversible.", @@ -1013,7 +967,6 @@ "view-all": "Voir tout", "filter-by-unread": "Filtrer par non lu", "mark-all-as-read": "Marquer comme lus", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Supprimer les lus", "allow-rename": "Autoriser le renommage", "allowRenamePopup-title": "Autoriser le renommage", @@ -1048,10 +1001,6 @@ "person": "Personne", "my-cards": "Mes Cartes", "card": "Carte", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Liste", "board": "Tableau", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Cacher tous les éléments de la check-list", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibilité", "accessibility-page-enabled": "Page d'accessibilité activée", "accessibility-info-not-added-yet": "L'information d'accessibilité n'a pas encore été ajoutée", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Temps", - "cron-error-message": "Error Message", - "cron-error-details": "Détails", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Terminé", - "idle": "Inactif", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Début", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1489,74 +1403,8 @@ "automatic-migration": "Migration automatique", "back-to-settings": "Retour aux paramètres", "board-id": "ID du tableau", - "board-migration": "Migration du tableau", - "board-migrations": "Migrations de tableau", + "board-migration": "Board Migration", "card-show-lists-on-minicard": "Afficher les listes sur la mini-carte", - "comprehensive-board-migration": "Migration complète de tableau", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Supprimer les listes vides en doublon ? ", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Cartes perdues", - "lost-cards-list": "Éléments restaurés", - "restore-lost-cards-migration": "Restaurer les cartes perdues", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Corriger les URLs d'avatar", - "fix-avatar-urls-migration-description": "Mets à jour les URLs d'avatar des participants du tableau pour utiliser le bon gestionnaire de stockage et corriger les références défaillantes d'avatar ", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration requise", - "migration-complete": "Terminé", - "migration-running": "En cours ...", - "migration-successful": "Migration terminée avec succès", - "migration-failed": "Migration en échec", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "Aucun problème détecté", - "run-migration": "Lancer la migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Migration du tableau en cours", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Étape courante", - "migration-progress-status": "Statut", - "migration-progress-details": "Détails", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Corriger les cartes orphelines", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Valider la migration", - "step-fix-avatar-urls": "Corriger les URLs d'avatar", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finaliser", - "step-delete-duplicate-empty-lists": "Supprimer les listes vides en doublon ? ", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restauration des listes", - "step-restore-cards": "Restaurer les cartes", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Réparation des IDs manquants", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Vérification des pièces jointes du tableau", - "step-fix-file-urls": "Réparation des URL de fichier", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Terminé", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Afficher la liste sur la mini-carte", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Taille totale", "unmigrated-boards": "Tableaux non migrés", "weight": "Poids", - "cron": "Planification", - "current-step": "Étape courante", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirmer", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Inactif", + "complete": "Terminé", + "cron": "Planification" } diff --git a/imports/i18n/data/fy-NL.i18n.json b/imports/i18n/data/fy-NL.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/fy-NL.i18n.json +++ b/imports/i18n/data/fy-NL.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/fy.i18n.json b/imports/i18n/data/fy.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/fy.i18n.json +++ b/imports/i18n/data/fy.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/gl-ES.i18n.json b/imports/i18n/data/gl-ES.i18n.json index 16d80aef9..8c523be4e 100644 --- a/imports/i18n/data/gl-ES.i18n.json +++ b/imports/i18n/data/gl-ES.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Engadir anexo", @@ -98,7 +86,6 @@ "add-card": "Engadir tarxeta", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Engadir lista", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Engadir membros", "added": "Added", - "addMemberPopup-title": "Engadir membros", + "addMemberPopup-title": "Membros", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Pode ver e editar tarxetas, retirar membros e cambiar a configuración do taboleiro.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Cambiar os permisos", "change-settings": "Cambiar a configuración", "changeAvatarPopup-title": "Cambiar o avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Cambiar de idioma", "changePasswordPopup-title": "Cambiar o contrasinal", "changePermissionsPopup-title": "Cambiar os permisos", @@ -335,16 +316,10 @@ "comment-placeholder": "Escribir un comentario", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computador", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Crear", "createBoardPopup-title": "Crear taboleiro", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Importar taboleiro", "createLabelPopup-title": "Crear etiqueta", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Data", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Rexeitar", "default-avatar": "Avatar predeterminado", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Listas", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Pechar a sesión", "log-in": "Acceder", "loginPopup-title": "Acceder", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Membros", "menu": "Menú", "move-selection": "Mover selección", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Mover abaixo de todo", "moveCardToTop-title": "Mover arriba de todo", "moveSelectionPopup-title": "Mover selección", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Selección múltipla", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "Non hai resultados", "normal": "Normal", "normal-desc": "Pode ver e editar tarxetas. Non pode cambiar a configuración.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "O convite aínda non foi aceptado", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Hora", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/gl.i18n.json b/imports/i18n/data/gl.i18n.json index 2413c8127..9b9438f28 100644 --- a/imports/i18n/data/gl.i18n.json +++ b/imports/i18n/data/gl.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Engadir anexo", @@ -98,7 +86,6 @@ "add-card": "Engadir tarxeta", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Engadir lista", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Engadir membros", "added": "Added", - "addMemberPopup-title": "Engadir membros", + "addMemberPopup-title": "Membros", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Pode ver e editar tarxetas, retirar membros e cambiar a configuración do taboleiro.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Cambiar os permisos", "change-settings": "Cambiar a configuración", "changeAvatarPopup-title": "Cambiar de avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Cambiar de idioma", "changePasswordPopup-title": "Cambiar o contrasinal", "changePermissionsPopup-title": "Cambiar os permisos", @@ -335,16 +316,10 @@ "comment-placeholder": "Escribir un comentario", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computador", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Crear", "createBoardPopup-title": "Crear taboleiro", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Importar taboleiro", "createLabelPopup-title": "Crear etiqueta", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Data", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Rexeitar", "default-avatar": "Avatar predeterminado", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Listas", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Pechar a sesión", "log-in": "Acceder", "loginPopup-title": "Acceder", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Membros", "menu": "Menú", "move-selection": "Mover selección", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Mover abaixo de todo", "moveCardToTop-title": "Mover arriba de todo", "moveSelectionPopup-title": "Mover selección", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Selección múltipla", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "Non hai resultados", "normal": "Normal", "normal-desc": "Pode ver e editar tarxetas. Non pode cambiar a configuración.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "O convite aínda non foi aceptado", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Hora", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/gu-IN.i18n.json b/imports/i18n/data/gu-IN.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/gu-IN.i18n.json +++ b/imports/i18n/data/gu-IN.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/he-IL.i18n.json b/imports/i18n/data/he-IL.i18n.json index f3adc801c..b34277bac 100644 --- a/imports/i18n/data/he-IL.i18n.json +++ b/imports/i18n/data/he-IL.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/he.i18n.json b/imports/i18n/data/he.i18n.json index 8021ddeda..2b6d8f5ef 100644 --- a/imports/i18n/data/he.i18n.json +++ b/imports/i18n/data/he.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "התגובה %s נמחקה", "activity-receivedDate": "תאריך הקבלה השתנה מ־%s ל־%s", "activity-startDate": "תאריך ההתחלה השתנה מ־%s ל־%s", - "allboards.starred": "סומן בכוכב", - "allboards.templates": "תבניות", - "allboards.remaining": "נותרו", - "allboards.workspaces": "מרחבי עבודה", - "allboards.add-workspace": "הוספת מרחב עבודה", - "allboards.add-workspace-prompt": "שם מרחב עבודה", - "allboards.add-subworkspace": "הוספת תת־מרחב עבודה", - "allboards.add-subworkspace-prompt": "שם תת־מרחב עבודה", - "allboards.edit-workspace": "עריכת מרחב עבודה", - "allboards.edit-workspace-name": "שם מרחב עבודה", - "allboards.edit-workspace-icon": "סמל מרחב עבודה (markdown)", - "multi-selection-active": "יש לסמן את התיבות כדי לבחור לוחות", "activity-dueDate": "תאריך היעד השתנה מ־%s ל־%s", "activity-endDate": "תאריך הסיום השתנה מ־%s ל־%s", "add-attachment": "הוספת קובץ מצורף", @@ -98,7 +86,6 @@ "add-card": "הוספת כרטיס", "add-card-to-top-of-list": "הוספת כרטיס לראש הרשימה", "add-card-to-bottom-of-list": "הוספת כרטיס לתחתית הרשימה", - "addListPopup-title": "הוספת רשימה", "setListWidthPopup-title": "הגדרת רוחבים", "set-list-width": "הגדרת רוחבים", "set-list-width-value": "הגדרת רוחבים מזעריים ומרביים (פיקסלים)", @@ -122,10 +109,10 @@ "add-after-list": "הוספה אחרי הרשימה", "add-members": "הוספת חברים", "added": "התווסף", - "addMemberPopup-title": "הוספת חברים", + "addMemberPopup-title": "חברים", "memberPopup-title": "הגדרות חברות", "admin": "מנהל", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "יש הרשאות לצפייה ולעריכת כרטיסים, להסרת חברים ולשינוי הגדרות לוח.", "admin-announcement": "הכרזה", "admin-announcement-active": "הכרזת מערכת פעילה", "admin-announcement-title": "הכרזה ממנהל המערכת", @@ -167,16 +154,12 @@ "board-background-image-url": "כתובת תמונת רקע", "add-background-image": "הוספת תמונת רקע", "remove-background-image": "הסרת תמונת רקע", - "show-at-all-boards-page": "הצגה בעמוד כל הלוחות", - "board-info-on-my-boards": "הגדרות כל הלוחות", - "boardInfoOnMyBoardsPopup-title": "הגדרות כל הלוחות", + "show-at-all-boards-page" : "הצגה בעמוד כל הלוחות", + "board-info-on-my-boards" : "הגדרות כל הלוחות", + "boardInfoOnMyBoardsPopup-title" : "הגדרות כל הלוחות", "boardInfoOnMyBoards-title": "הגדרות כל הלוחות", "show-card-counter-per-list": "הצגת מספור כרטיסים לפי רשימה", "show-board_members-avatar": "הצגת התמונות הייצוגיות של חברי הלוח", - "board_members": "כל החברים בלוח", - "card_members": "כל החברים של הכרטיס הנוכחי בלוח הזה", - "board_assignees": "כל המוקצים לכל הכרטיסים בלוח הזה", - "card_assignees": "כל המוקצים לכרטיס הנוכחי בלוח הזה", "board-nb-stars": "%s כוכבים", "board-not-found": "לוח לא נמצא", "board-private-info": "לוח זה יהיה <strong>פרטי</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "שינוי הרשאות", "change-settings": "שינוי הגדרות", "changeAvatarPopup-title": "החלפת תמונת משתמש", - "delete-avatar-confirm": "למחוק את התמונה הייצוגית הזאת?", - "deleteAvatarPopup-title": "למחוק תמונה ייצוגית?", "changeLanguagePopup-title": "החלפת שפה", "changePasswordPopup-title": "החלפת ססמה", "changePermissionsPopup-title": "שינוי הרשאות", @@ -335,16 +316,10 @@ "comment-placeholder": "כתיבת הערה", "comment-only": "תגובות בלבד", "comment-only-desc": "ניתן להגיב על כרטיסים בלבד.", - "comment-assigned-only": "רק תגובה מוקצית", - "comment-assigned-only-desc": "רק כרטיסים מוקצים גלויים. אפשר להעיר בלבד.", "comment-delete": "למחוק את ההערה?", "deleteCommentPopup-title": "למחוק הערה?", "no-comments": "אין הערות", - "no-comments-desc": "לא ניתן לצפות בהערות.", - "read-only": "לקריאה בלבד", - "read-only-desc": "אפשר לצפות בכרטיסים בלבד. אי אפשר לערוך.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "לא ניתן לצפות בתגובות ובפעילויות.", "worker": "עובד/ת", "worker-desc": "אפשר רק להעביר כרטיסים, להקצות כרטיסים לעצמו/ה ולהגיב.", "computer": "מחשב", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "למחוק את רשימת המטלות?", "subtaskDeletePopup-title": "למחוק תת־משימה?", "checklistDeletePopup-title": "למחוק רשימת מטלות?", - "checklistItemDeletePopup-title": "למחוק פריט רשימת סימון?", "copy-card-link-to-clipboard": "העתקת קישור הכרטיס ללוח הגזירים", "copy-text-to-clipboard": "העתקת טקסט ללוח הגזירים", "linkCardPopup-title": "קישור כרטיס", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"כותרת כרטיס ראשון\", \"description\":\"תיאור כרטיס ראשון\"}, {\"title\":\"כותרת כרטיס שני\",\"description\":\"תיאור כרטיס שני\"},{\"title\":\"כותרת כרטיס אחרון\",\"description\":\"תיאור כרטיס אחרון\"} ]", "create": "יצירה", "createBoardPopup-title": "יצירת לוח", - "createTemplateContainerPopup-title": "הוספת מכולה לתבנית", "chooseBoardSourcePopup-title": "ייבוא לוח", "createLabelPopup-title": "יצירת תווית", "createCustomField": "יצירת שדה", @@ -385,7 +358,7 @@ "date": "תאריך", "date-format": "תבנית תאריך", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "סירוב", "default-avatar": "תמונת משתמש כבררת מחדל", @@ -412,7 +385,7 @@ "editNotificationPopup-title": "שינוי דיווח", "editProfilePopup-title": "עריכת פרופיל", "email": "דוא״ל", - "email-address": "כתובת דוא״ל", + "email-address": "Email Address", "email-enrollAccount-subject": "נוצר עבורך חשבון באתר __siteName__", "email-enrollAccount-text": "__user__ שלום,\n\nכדי להתחיל להשתמש בשירות, יש ללחוץ על הקישור המופיע להלן.\n\n__url__\n\nתודה.", "email-fail": "שליחת ההודעה בדוא״ל נכשלה", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "ניתן לשמור רשימה בארכיון כדי להסיר אותה מהלוח ולשמור על היסטוריית הפעילות.", "lists": "רשימות", "swimlanes": "מסלולים", - "calendar": "לוח שנה", - "gantt": "גאנט", "log-out": "יציאה", "log-in": "כניסה", "loginPopup-title": "כניסה", "memberMenuPopup-title": "הגדרות חברות", - "grey-icons": "סמלים אפרים", "members": "חברים", "menu": "תפריט", "move-selection": "העברת בחירה", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "העברה לתחתית הרשימה", "moveCardToTop-title": "העברה לראש הרשימה", "moveSelectionPopup-title": "העברת בחירה", - "copySelectionPopup-title": "העתקת הבחירה", - "selection-color": "צבע בחירה", "multi-selection": "בחירה מרובה", "multi-selection-label": "הגדרת תווית לבחירה", "multi-selection-member": "הגדרת חבר לבחירה", @@ -587,8 +555,6 @@ "no-results": "אין תוצאות", "normal": "רגיל", "normal-desc": "הרשאה לצפות ולערוך כרטיסים. לא ניתן לשנות הגדרות.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "רק כרטיסים מוקצים גלויים. עריכה כמשתמש רגיל.", "not-accepted-yet": "ההזמנה לא אושרה עדיין", "notify-participate": "קבלת עדכונים על כרטיסים בהם יש לך מעורבות הן בתהליך היצירה והן בחברות", "notify-watch": "קבלת עדכונים על כל לוח, רשימה או כרטיס שסימנת למעקב", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "לאפשר שינוי דוא״ל", "accounts-allowUserNameChange": "לאפשר שינוי שם משתמש", "tableVisibilityMode-allowPrivateOnly": "הופעה של לוחות: לאפשר לוחות פרטיים בלבד", - "tableVisibilityMode": "הופעה של לוחות", + "tableVisibilityMode" : "הופעה של לוחות", "createdAt": "נוצר ב", "modifiedAt": "נערך ב־", "verified": "עבר אימות", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "החלפת מועד הקבלה", "editCardEndDatePopup-title": "החלפת מועד הסיום", "setCardColorPopup-title": "הגדרת צבע", - "setSelectionColorPopup-title": "הגדרת צבע בחירה", "setCardActionsColorPopup-title": "בחירת צבע", "setSwimlaneColorPopup-title": "בחירת צבע", "setListColorPopup-title": "בחירת צבע", @@ -790,10 +755,8 @@ "delete-board-confirm-popup": "כל הרשימות, הכרטיסים, התווית והפעולות יימחקו ולא תהיה לך דרך לשחזר את תכני הלוח. אין אפשרות לבטל.", "boardDeletePopup-title": "למחוק את הלוח?", "delete-board": "מחיקת לוח", - "delete-all-notifications": "למחוק את כל ההתראות", - "delete-all-notifications-confirm": "למחוק את כל ההתראות? זאת פעולה בלתי הפיכה.", - "delete-duplicate-lists": "מחיקת רשימות כפולות", - "delete-duplicate-lists-confirm": "להמשיך? הפעולה הזאת תמחק את כל הרשימות הכפולות שיש להן את אותו השם ואינן מכילות כרטיסים.", + "delete-duplicate-lists": "Delete Duplicate Lists", + "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "תת־משימות עבור הלוח __board__", "default": "בררת מחדל", "defaultdefault": "בררת מחדל", @@ -942,13 +905,6 @@ "authentication-method": "שיטת אימות", "authentication-type": "סוג אימות", "custom-product-name": "שם מותאם אישית למוצר", - "custom-head-tags-enabled": "הפעלת תגיות כותרת (head) מותאמות אישית", - "custom-head-meta-tags": "תגיות על (meta) מותאמות אישית (HTML)", - "custom-head-link-tags": "תגיות קישור (link) מותאמות אישית (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "הפעלת assetlinks.json בהתאמה אישית", - "custom-assetlinks-content": "תוכן assetlinks.json מותאם אישית (JSON)", "layout": "פריסה", "hide-logo": "הסתרת לוגו", "hide-card-counter-list": "הסתרת רשימת ספירת כרטיסים בכל הלוחות", @@ -979,8 +935,6 @@ "a-endAt": "מועד הסיום השתנה לכדי", "a-startAt": "מועד ההתחלה השתנה לכדי", "a-receivedAt": "מועד הקבלה השתנה לכדי", - "above-selected-card": "מעל הכרטיס הנבחר", - "below-selected-card": "מתחת לכרטיס הנבחר", "almostdue": "מועד היעד הנוכחי %s מתקרב", "pastdue": "מועד היעד הנוכחי %s חלף", "duenow": "מועד היעד הנוכחי %s הוא היום", @@ -989,7 +943,7 @@ "act-almostdue": "הזכירה שמועד היעד הנוכחי (__timeValue__) של __card__ מתקרב", "act-pastdue": "הזכירה שמועד היעד הנוכחי (__timeValue__) של __card__ חלף", "act-duenow": "הזכירה שמועד היעד הנוכחי (__timeValue__) של __card__ הוא עכשיו", - "act-atUserComment": "הוזכרת בכרטיס __card__: __comment__ ברשימה __list__ במסלול __swimlane__ בלוח __board__", + "act-atUserComment": "אוזכרת תחת [__board__] __list__/__card__", "delete-user-confirm-popup": "למחוק את החשבון הזה? אי אפשר לבטל.", "delete-team-confirm-popup": "למחוק את הצוות הזה? אי אפשר לשחזר.", "delete-org-confirm-popup": "למחוק את הארגון הזה? אי אפשר לשחזר.", @@ -1013,7 +967,6 @@ "view-all": "להציג הכול", "filter-by-unread": "סימון לפי כאלו שלא נקראו", "mark-all-as-read": "לסמן הכול כאילו שנקראו", - "mark-all-as-unread": "לסמן הכול כאילו שנקראו", "remove-all-read": "הסרת כל אלו שנקראו", "allow-rename": "לאפשר שינוי שם", "allowRenamePopup-title": "לאפשר שינוי שם", @@ -1048,10 +1001,6 @@ "person": "איש/ה", "my-cards": "הכרטיסים שלי", "card": "כרטיס", - "today": "היום", - "day": "יום", - "week": "שבוע", - "month": "חודש", "list": "רשימה", "board": "לוח", "context-separator": "/", @@ -1069,8 +1018,8 @@ "dueCardsViewChange-choice-me": "אני", "dueCardsViewChange-choice-all": "כל המשתמשים", "dueCardsViewChange-choice-all-description": "מציג את כל הכרטיסים שלא הושלמו ושיש להם *תוקף* מלוחות שלמשתמש יש הרשאה לגשת אליהם.", - "dueCards-noResults-title": "לא נמצאו כרטיסים שתוקפם פג", - "dueCards-noResults-description": "אין לך כרטיסים עם תאריכי סיום כרגע.", + "dueCards-noResults-title": "No Due Cards Found", + "dueCards-noResults-description": "You don't have any cards with due dates at the moment.", "broken-cards": "כרטיסים פגומים", "board-title-not-found": "הלוח ‚%s’ לא נמצא.", "swimlane-title-not-found": "המסלול ‚%s’ לא נמצא.", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "הסתרת כל הפריטים ברשימת המטלות", "support": "תמיכה", "supportPopup-title": "תמיכה", - "support-page-enabled": "עמוד התמיכה פעיל", - "support-info-not-added-yet": "לא נוספו פרטי תמיכה עדיין", - "support-info-only-for-logged-in-users": "פרטי התמיכה הם למשתמשים שנכנסו למערכת בלבד.", - "support-title": "כותרת תמיכה", - "support-content": "תוכן תמיכה", "accessibility": "נגישות", "accessibility-page-enabled": "עמוד הנגישות הופעל", "accessibility-info-not-added-yet": "פרטי הנגישות לא נוספו עדיין", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "המשימה המתוזמנת נמחקה בהצלחה", "cron-job-pause-failed": "השהיית משימה מתוזמנת נכשלה", "cron-job-paused": "המשימה המתוזמנת הושהתה בהצלחה", - "cron-job-resume-failed": "המשך משימה מתוזמנת נכשל", - "cron-job-resumed": "משימה מתוזמנת נמשכה בהצלחה", - "cron-job-start-failed": "התחלת משימה מתוזמנת נכשלה", - "cron-job-started": "המשימה המתוזמנת הופעלה בהצלחה", - "cron-migration-errors": "שגיאות הסבה", - "cron-migration-warnings": "אזהרות הסבה", - "cron-no-errors": "אין שגיאות להצגה", - "cron-error-severity": "דרגת חומרה", - "cron-error-time": "זמן", - "cron-error-message": "הודעת שגיאה", - "cron-error-details": "פרטים", - "cron-clear-errors": "פינוי כל השגיאות", - "cron-retry-failed": "לנסות הסבות שנכשלו מחדש", - "cron-resume-paused": "להמשיך הסבות שנעצרו", - "cron-errors-cleared": "כל השגיאות פונו בהצלחה", - "cron-no-failed-migrations": "אין הסבות כושלות לנסות שוב", - "cron-no-paused-migrations": "אין הסבות שנעצרו לחידוש", - "cron-migrations-resumed": "ההסבות חודשו בהצלחה", - "cron-migrations-retried": "הסבות שנכשלו נוסו מחדש בהצלחה", - "complete": "הושלם", - "idle": "בהמתנה", "filesystem-path-description": "נתיב בסיס לאחסון קבצים", "gridfs-enabled": "GridFS הופעל", "gridfs-enabled-description": "להשתמש ב־GridFS מבית MongoDB לאחסון קבצים", - "all-migrations": "כל ההסבות", - "select-migration": "בחירת הסבה", - "start": "התחלה", - "pause": "השהיה", - "stop": "עצירה", - "migration-starting": "ההסבות מתחילות…", - "migration-pausing": "ההסבות מושהות…", - "migration-stopping": "ההסבות נעצרות…", "migration-pause-failed": "השהיית ההסבות נכשלה", "migration-paused": "ההסבות הושהו בהצלחה", "migration-progress": "התקדמות הסבה", "migration-start-failed": "התחלת ההסבות נכשלה", "migration-started": "ההסבות החלו בהצלחה", - "migration-not-needed": "אין צורך בהסבה", "migration-status": "מצב הסבה", "migration-stop-confirm": "לעצור את כל ההסבות?", "migration-stop-failed": "עצירת ההסבות נכשלה", @@ -1490,73 +1404,7 @@ "back-to-settings": "חזרה להגדרות", "board-id": "מזהה לוח", "board-migration": "הסבת לוחות", - "board-migrations": "הסבות לוחות", "card-show-lists-on-minicard": "הצגת רשימות בכרטיסון", - "comprehensive-board-migration": "הסבת לוחות נרחבת", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "מחיקת רשימות ריקות כפולות", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "כרטיסים אבודים", - "lost-cards-list": "פריטים משוחזרים", - "restore-lost-cards-migration": "שחזור כרטיסים אבודים", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "תיקון רשימות חסרות", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "תיקון כתובות תמונות ייצוגיות", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "תיקון כל כתובות הקבצים", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "נדרשת הסבה", - "migration-complete": "הושלם", - "migration-running": "Running...", - "migration-successful": "ההסבה הושלמה בהצלחה", - "migration-failed": "ההסבה נכשלה", - "migrations": "הסבות", - "migrations-admin-only": "רק מנהלי לוחות יכולים להריץ הסבות", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "לא נמצאו בעיות", - "run-migration": "הרצת הסבה", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "הפעולה הזאת תזהה ותתקן רשימות חסות או פגומות במבנה הלוח. להמשיך?", - "run-fix-avatar-urls-migration-confirm": "הפעולה הזאת תעדכן את כתובות התמונות הייצוגיות לחברי הלוח כך שישתמשו במנגנון האחסון הנכון. להמשיך?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "מתבצעת הסבת לוח", - "migration-progress-overall": "סך כל ההתקדמות", - "migration-progress-current-step": "השלב הנוכחי", - "migration-progress-status": "מצב", - "migration-progress-details": "פרטים", - "migration-progress-note": "נא להמתין בזמן שאנו מסכים את הלוח שלך למבנה העדכני ביותר…", - "steps": "שלבים", - "view": "תצוגה", - "has-swimlanes": "יש מסלולים", - - "step-analyze-board-structure": "ניתוח מבנה לוח", - "step-fix-orphaned-cards": "תיקון כרטיסים יתומים", - "step-convert-shared-lists": "המרת רשימות משותפות", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "אימות הסבה", - "step-fix-avatar-urls": "תיקון כתובות תמונות ייצוגיות", - "step-fix-attachment-urls": "תיקון כתובות צרופות", - "step-analyze-lists": "ניתוח רשימות", - "step-create-missing-lists": "יצירת הרשימות החסרות", - "step-update-cards": "עדכון כרטיסים", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "מחיקת רשימות ריקות כפולות", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "שחזור רשימות", - "step-restore-cards": "שחזור כרטיסים", - "step-restore-swimlanes": "שחזור מסלולים", - "step-fix-missing-ids": "תיקון מזהים חסרים", - "step-scan-users": "התמונות הייצוגיות של החברים בלוח נבדקות", - "step-scan-files": "הקבצים המצורפים ללוח נבדקים", - "step-fix-file-urls": "כתובות הקבצים מתוקנות", "cleanup": "ניקיון", "cleanup-old-jobs": "ניקוי משימות ישנות", "completed": "הושלמה", @@ -1637,7 +1485,6 @@ "schedule": "לוח זמנים", "search-boards-or-operations": "חיפוש לוחות או פעולות…", "show-list-on-minicard": "הצגת רשימה בכרטיסון", - "showChecklistAtMinicard": "הצגת רשימת מטלות על הכרטיסון", "showing": "מוצג", "start-test-operation": "התחלת פעולת בדיקה", "start-time": "מועד התחלה", @@ -1650,37 +1497,7 @@ "total-size": "גודל כולל", "unmigrated-boards": "לוחות שלא הוסבו", "weight": "משקל", - "cron": "מתזמן", - "current-step": "השלב הנוכחי", - "otp": "קוד חד־פעמי", - "create-account": "יצירת חשבון", - "already-account": "כבר יש לך חשבון? אפשר להיכנס", - "available-repositories": "מאגרים זמינים", - "repositories": "מאגרים", - "repository": "מאגר", - "repository-name": "שם המאגר", - "size-bytes": "גודל (בתים)", - "last-modified": "שינוי אחרון", - "no-repositories": "לא נמצאו מאגרים", - "create-repository": "יצירת מאגר", - "upload-repository": "העלאת/עדכון מאגר", - "api-endpoints": "נקודות גישה ל־API", - "sign-in-to-upload": "יש להיכנס כדי להעלות מאגרים", - "account-locked": "החשבון נעול זמנית עקב יותר מדי ניסיונות גישה כושלים. נא לנסות שוב מאוחר יותר.", - "otp-required": "צריך קוד חד־פעמי", - "invalid-credentials": "שם משתמש או סיסמה שגויים", - "username-password-required": "חובה למלא שם משתמש וסיסמה", - "password-mismatch": "הסיסמאות לא תואמות", - "username-too-short": "אורך שם המשתמש חייב להיות 3 תווים או יותר", - "user-exists": "המשתמש כבר קיים", - "account-created": "החשבון נוצר! אפשר להיכנס עכשיו.", - "account-creation-failed": "יצירת החשבון נכשלה", - "login": "כניסה", - "confirm": "אישור", - "error": "שגיאה", - "file": "קובץ", - "log": "יומן", - "logout": "יציאה", - "server": "שרת", - "protocol": "פרוטוקול" + "idle": "בהמתנה", + "complete": "הושלם", + "cron": "מתזמן" } diff --git a/imports/i18n/data/hi-IN.i18n.json b/imports/i18n/data/hi-IN.i18n.json index 31a76f114..e65f94bf7 100644 --- a/imports/i18n/data/hi-IN.i18n.json +++ b/imports/i18n/data/hi-IN.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "टिप्पणी हटा दी गई", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "खाका", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "संलग्न करें", @@ -98,7 +86,6 @@ "add-card": "कार्ड जोड़ें", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "सूची जोड़ें", "setListWidthPopup-title": "चौड़ाई सेट करें", "set-list-width": "चौड़ाई सेट करें", "set-list-width-value": "न्यूनतम और अधिकतम चौड़ाई (पिक्सेल) सेट करें", @@ -122,10 +109,10 @@ "add-after-list": "सूची के बाद जोड़ें", "add-members": "सदस्य जोड़ें", "added": "जोड़ा गया", - "addMemberPopup-title": "सदस्य जोड़ें", + "addMemberPopup-title": "सदस्य", "memberPopup-title": "सदस्य व्यवस्था", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "कार्ड देख और संपादित कर सकते हैं, सदस्यों को हटा सकते हैं, और बोर्ड के लिए सेटिंग्स बदल सकते हैं।", "admin-announcement": "घोषणा", "admin-announcement-active": "सक्रिय सिस्टम-व्यापी घोषणा", "admin-announcement-title": "घोषणा प्रशासक से", @@ -167,16 +154,12 @@ "board-background-image-url": "पृष्ठभूमि छवि URL", "add-background-image": "पृष्ठभूमि छवि जोड़ें ", "remove-background-image": "पृष्ठभूमि छवि निकालें", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s पसंद होना", "board-not-found": "बोर्ड नहीं मिला", "board-private-info": "यह बोर्ड हो जाएगा <strong>निजी</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "अनुमतियां परिवर्तित करें", "change-settings": "व्यवस्था परिवर्तित करें", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "भाषा परिवर्तन करें", "changePasswordPopup-title": "गोपनीयता परिवर्तन करें", "changePermissionsPopup-title": "अनुमतियां परिवर्तित करें", @@ -335,16 +316,10 @@ "comment-placeholder": "टिप्पणी लिखें", "comment-only": "केवल टिप्पणी करें", "comment-only-desc": "केवल कार्ड पर टिप्पणी कर सकते हैं।", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "कोई टिप्पणी नहीं", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "टिप्पणियां और गतिविधियां नहीं देख पा रहे हैं।", "worker": "कामगार", "worker-desc": "केवल कार्ड ले जा सकते हैं, खुद को कार्ड और टिप्पणी करने के लिए असाइन करें।", "computer": "संगणक", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "कॉपी कार्ड क्लिपबोर्ड करने के लिए लिंक", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "कार्ड कड़ी", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[{\"title\":\"पहला कार्ड शीर्षक\",\"description\":\"पहला कार्ड विवरण\"},{\"title\":\"दूसरा कार्ड शीर्षक\",\"description\":\"दूसरा कार्ड विवरण\"},{\"title\":\"अंतिम कार्ड शीर्षक\",\"description\":\"अंतिम कार्ड विवरण\" }]", "create": "निर्माण करना", "createBoardPopup-title": "बोर्ड निर्माण करना", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "बोर्ड आयात", "createLabelPopup-title": "नामपत्र निर्माण", "createCustomField": "क्षेत्र निर्माण", @@ -385,7 +358,7 @@ "date": "दिनांक", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "तैरना", - "calendar": "तिथि-पत्र", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "सदस्य व्यवस्था", - "grey-icons": "Grey Icons", "members": "सदस्य", "menu": "Menu", "move-selection": "स्थानांतरित selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "स्थानांतरित तक Bottom", "moveCardToTop-title": "स्थानांतरित तक Top", "moveSelectionPopup-title": "स्थानांतरित selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can आलोकन और संपादित करें कार्ड. Can't change व्यवस्था.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates तक any बोर्ड, lists, or कार्ड you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, कार्ड,नामपत्र , और activities हो जाएगा deleted और you won't be able तक recover the बोर्ड contents. There is no undo.", "boardDeletePopup-title": "मिटाएँ बोर्ड?", "delete-board": "मिटाएँ बोर्ड", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ बोर्ड", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "कार्ड", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "सभी चेकलिस्ट आइटम छुपाएं", "support": "समर्थन या सहायता", "supportPopup-title": "समर्थन या सहायता", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "अभिगम्यता पृष्ठ सक्षम किया गया", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "प्रारंभ", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/hi.i18n.json b/imports/i18n/data/hi.i18n.json index 0f0b7a7d1..8febb7ce7 100644 --- a/imports/i18n/data/hi.i18n.json +++ b/imports/i18n/data/hi.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "टिप्पणी हटा दी गई", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "खाका", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "संलग्न करें", @@ -98,7 +86,6 @@ "add-card": "कार्ड जोड़ें", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "सूची जोड़ें", "setListWidthPopup-title": "चौड़ाई सेट करें", "set-list-width": "चौड़ाई सेट करें", "set-list-width-value": "न्यूनतम और अधिकतम चौड़ाई सेट करें (पिक्सेल)", @@ -122,10 +109,10 @@ "add-after-list": "सूची के बाद जोड़ें", "add-members": "सदस्य जोड़ें", "added": "जोड़ा गया", - "addMemberPopup-title": "सदस्य जोड़ें", + "addMemberPopup-title": "सदस्य", "memberPopup-title": "सदस्य व्यवस्था", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "कार्ड देख और संपादित कर सकते हैं, सदस्यों को हटा सकते हैं, और बोर्ड के लिए सेटिंग्स बदल सकते हैं।", "admin-announcement": "घोषणा", "admin-announcement-active": "सक्रिय सिस्टम-व्यापी घोषणा", "admin-announcement-title": "घोषणा प्रशासक से", @@ -167,16 +154,12 @@ "board-background-image-url": "पृष्ठभूमि छवि URL", "add-background-image": "पृष्ठभूमि छवि जोड़ें", "remove-background-image": "पृष्ठभूमि छवि हटाएं", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s पसंद होना", "board-not-found": "बोर्ड नहीं मिला", "board-private-info": "यह बोर्ड हो जाएगा <strong>निजी</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "अनुमतियां परिवर्तित करें", "change-settings": "व्यवस्था परिवर्तित करें", "changeAvatarPopup-title": "अवतार परिवर्तन करें", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "भाषा परिवर्तन करें", "changePasswordPopup-title": "गोपनीयता परिवर्तन करें", "changePermissionsPopup-title": "अनुमतियां परिवर्तित करें", @@ -335,16 +316,10 @@ "comment-placeholder": "टिप्पणी लिखें", "comment-only": "केवल टिप्पणी करें", "comment-only-desc": "केवल कार्ड पर टिप्पणी कर सकते हैं।", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "कोई टिप्पणी नहीं", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "टिप्पणियां और गतिविधियां नहीं देख पा रहे हैं।", "worker": "कामगार", "worker-desc": "केवल कार्ड ले जा सकते हैं, खुद को कार्ड और टिप्पणी करने के लिए असाइन करें।", "computer": "संगणक", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "कॉपी कार्ड क्लिपबोर्ड करने के लिए लिंक", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "कार्ड कड़ी", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[{\"title\":\"पहला कार्ड शीर्षक\",\"description\":\"पहला कार्ड विवरण\"},{\"title\":\"दूसरा कार्ड शीर्षक\",\"description\":\"दूसरा कार्ड विवरण\"},{\"title\":\"अंतिम कार्ड शीर्षक\",\"description\":\"अंतिम कार्ड विवरण\" }]", "create": "निर्माण करना", "createBoardPopup-title": "बोर्ड निर्माण करना", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "बोर्ड आयात", "createLabelPopup-title": "नामपत्र निर्माण", "createCustomField": "क्षेत्र निर्माण", @@ -385,7 +358,7 @@ "date": "दिनांक", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "सूचियाँ", "swimlanes": "तैरना", - "calendar": "तिथि-पत्र", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "सदस्य व्यवस्था", - "grey-icons": "Grey Icons", "members": "सदस्य", "menu": "Menu", "move-selection": "स्थानांतरित selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "स्थानांतरित तक Bottom", "moveCardToTop-title": "स्थानांतरित तक Top", "moveSelectionPopup-title": "स्थानांतरित selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can आलोकन और संपादित करें कार्ड. Can't change व्यवस्था.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates तक any बोर्ड, lists, or कार्ड you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, कार्ड,नामपत्र , और activities हो जाएगा deleted और you won't be able तक recover the बोर्ड contents. There is no undo.", "boardDeletePopup-title": "मिटाएँ बोर्ड?", "delete-board": "मिटाएँ बोर्ड", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ बोर्ड", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "कार्ड", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "सभी चेकलिस्ट आइटम छिपाएं", "support": "सहायता", "supportPopup-title": "सहायता", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "अभिगम्यता पृष्ठ सक्षम किया गया", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "प्रारंभ", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/hr.i18n.json b/imports/i18n/data/hr.i18n.json index a709d0ec0..5b66d06dd 100644 --- a/imports/i18n/data/hr.i18n.json +++ b/imports/i18n/data/hr.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "obrisan komentar %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Predlošci", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Dodaj privitak", @@ -98,7 +86,6 @@ "add-card": "Dodaj karticu", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Dodaj listu", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Dodaj korisnika", "added": "Dodano", - "addMemberPopup-title": "Dodaj korisnika", + "addMemberPopup-title": "Korisnici", "memberPopup-title": "Member Settings", "admin": "Administrator", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Može pregledavati i uređivati ​​kartice, uklanjati članove i mijenjati postavke ploče.", "admin-announcement": "Obavijest", "admin-announcement-active": "Aktivne sistemske obavijesti", "admin-announcement-title": "Administratorske obavijesti", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s zvjezdica", "board-not-found": "Ploča nije pronađena", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Promijeni dozvole", "change-settings": "Promijeni postavke", "changeAvatarPopup-title": "Promijeni avatara", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Promijeni jezik", "changePasswordPopup-title": "Promijeni lozinku", "changePermissionsPopup-title": "Promijeni dozvole", @@ -335,16 +316,10 @@ "comment-placeholder": "Napiši komentar", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "Nema komentara", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Dodaj", "createBoardPopup-title": "Dodaj ploču", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Uvezi ploču", "createLabelPopup-title": "Dodaj oznaku", "createCustomField": "Dodaj polje", @@ -385,7 +358,7 @@ "date": "Datum", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Odustani", "default-avatar": "Zadani avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Liste", "swimlanes": "Trake", - "calendar": "Kalendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Korisnici", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "Bez rezultata", "normal": "Normalno", "normal-desc": "Može pregledavati i uređivati ​​kartice. Nije moguće promijeniti postavke.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Dozvoli promjenu Email-a", "accounts-allowUserNameChange": "Dozvoli promjenu korisničkog imena", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Obrisati ploču?", "delete-board": "Obriši ploču", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Kartica", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Vrijeme", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Početak", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/hu.i18n.json b/imports/i18n/data/hu.i18n.json index a3385893f..82d25500a 100644 --- a/imports/i18n/data/hu.i18n.json +++ b/imports/i18n/data/hu.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "törölte ezt a megjegyzést: %s", "activity-receivedDate": "átírta az \"érkezett\" dátumot erről: %s erre: %s", "activity-startDate": "átírta az \"elkezdve\" dátumot erről: %s erre: %s", - "allboards.starred": "Starred", - "allboards.templates": "Sablonok", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "átírta a *határidő* dátumát erről: %s erre: %s", "activity-endDate": "átírta a \"befejezés\" dátumát erről: %s erre: %s", "add-attachment": "Melléklet hozzáadása", @@ -98,7 +86,6 @@ "add-card": "Kártya hozzáadása", "add-card-to-top-of-list": "Kártya hozzáadás a Lista elejére", "add-card-to-bottom-of-list": "Kártya hozzáadás a Lista végére", - "addListPopup-title": "Lista hozzáadása", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Tagok hozzáadása", "added": "Hozzáadva", - "addMemberPopup-title": "Tagok hozzáadása", + "addMemberPopup-title": "Tagok", "memberPopup-title": "Tagok beállításai", "admin": "Adminisztrátor", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Megtekintheti és szerkesztheti a kártyákat, eltávolíthat tagokat, valamint megváltoztathatja a tábla beállításait.", "admin-announcement": "Bejelentés", "admin-announcement-active": "Bekapcsolt rendszerszintű bejelentés", "admin-announcement-title": "Bejelentés az adminisztrátortól", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s csillag", "board-not-found": "A tábla nem található", "board-private-info": "Ez a tábla legyen <strong>személyes</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Jogosultságok megváltoztatása", "change-settings": "Beállítások megváltoztatása", "changeAvatarPopup-title": "Avatár megváltoztatása", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Nyelv megváltoztatása", "changePasswordPopup-title": "Jelszó megváltoztatása", "changePermissionsPopup-title": "Jogosultságok megváltoztatása", @@ -335,16 +316,10 @@ "comment-placeholder": "Megjegyzés írása", "comment-only": "Csak megjegyzés", "comment-only-desc": "Csak megjegyzést írhat a kártyákhoz.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Biztosan törlöd a megjegyzést?", "deleteCommentPopup-title": "Törlöd a megjegyzést?", "no-comments": "Nincs megjegyzés", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Nem láthatsz hozzászólásokat és eseményeket", "worker": "Dolgozó", "worker-desc": "Csak mozgathat Kártyákat, hozzárendelheti magát Kártyákhoz és megjegyzésekhez", "computer": "Számítógép", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Kártya hivatkozásának másolása a vágólapra", "copy-text-to-clipboard": "Szöveg másolása vágólapra", "linkCardPopup-title": "Kártyára hivatkozás", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Első kártya címe\", \"description\":\"Első kártya leírása\"}, {\"title\":\"Második kártya címe\",\"description\":\"Második kártya leírása\"},{\"title\":\"Utolsó kártya címe\",\"description\":\"Utolsó kártya leírása\"} ]", "create": "Létrehozás", "createBoardPopup-title": "Tábla létrehozása", - "createTemplateContainerPopup-title": "Sablon Tároló hozzáadása", "chooseBoardSourcePopup-title": "Tábla importálása", "createLabelPopup-title": "Címke létrehozása", "createCustomField": "Mező létrehozása", @@ -385,7 +358,7 @@ "date": "Dátum", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Elutasítás", "default-avatar": "Alapértelmezett avatár", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Archiválhatsz egy Listát a Tábláról és megőrizheted a vele kapcsolatos korábbi eseményeket.", "lists": "Listák", "swimlanes": "Úszósávok", - "calendar": "Naptár", - "gantt": "Gantt", "log-out": "Kijelentkezés", "log-in": "Bejelentkezés", "loginPopup-title": "Bejelentkezés", "memberMenuPopup-title": "Tagok beállításai", - "grey-icons": "Grey Icons", "members": "Tagok", "menu": "Menü", "move-selection": "Kijelölés áthelyezése", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Áthelyezés az aljára", "moveCardToTop-title": "Áthelyezés a tetejére", "moveSelectionPopup-title": "Kijelölés áthelyezése", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Címke hozzáadása a kiválasztottakhoz", "multi-selection-member": "Tag hozzáadása a kiválasztottakhoz", @@ -587,8 +555,6 @@ "no-results": "Nincs találat", "normal": "Normál", "normal-desc": "Megtekintheti és szerkesztheti a kártyákat. Nem változtathatja meg a beállításokat.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "A meghívás még nincs elfogadva", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Frissítések fogadása bármely táblánál, listánál vagy kártyánál, amelyet megtekint", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "E-mail megváltoztatásának engedélyezése", "accounts-allowUserNameChange": "Felhasználónév megváltoztatásának engedélyezése", "tableVisibilityMode-allowPrivateOnly": "Tábla láthatóság: Csak privát táblák engedélyezettek", - "tableVisibilityMode": "Táblák láthatósága", + "tableVisibilityMode" : "Táblák láthatósága", "createdAt": "Létrehozva", "modifiedAt": "Módosult", "verified": "Ellenőrizve", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Érkezési dátum megváltoztatása", "editCardEndDatePopup-title": "Befejezési dátum megváltoztatása", "setCardColorPopup-title": "Szín beállítása", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Válassz színt", "setSwimlaneColorPopup-title": "Válassz színt", "setListColorPopup-title": "Válassz színt", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Minden Lista, Kártya, Címke és Esemény véglegesen törlésre kerül és nincs rá mód, hogy visszanyerd a Tábla tartalmát. Nincs visszavonási lehetőség sem.", "boardDeletePopup-title": "TÖRLÖD a Táblát?", "delete-board": "Tábla törlése", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Rész-feladatok ehhez a Táblához: __board__", @@ -942,13 +905,6 @@ "authentication-method": "Hitelesítési mód", "authentication-type": "Hitelesítés típusa", "custom-product-name": "Saját terméknév", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Elrendezés", "hide-logo": "Logo elrejtése", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "átírta a *vége* időpontját erre:", "a-startAt": "átírta a *kezdet* időpontját erre:", "a-receivedAt": "átírta az *érkezett* időpontját erre:", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "a határidő dátuma %s közeledik", "pastdue": "a határidő dátuma %s már lejárt", "duenow": "a határidő dátuma %s ma van", @@ -989,7 +943,7 @@ "act-almostdue": "emlékeztette a mostani határidő (__timeValue__) közeledtére ezen a Kártyán: __card__", "act-pastdue": "emlékeztette a mostani határidő (__timeValue__) elmúltára ezen a Kártyán: __card__", "act-duenow": "emlékeztette, hogy a mostani határidő (__timeValue__) ma van ezen a Kártyán: __card__", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Megemlítettek a [__board__] Tábla __list__ listáján ezen a kártyán: __card__", "delete-user-confirm-popup": "Biztosan törlöd ezt a fiókot? Nem lesz visszavonási lehetőséged!", "delete-team-confirm-popup": "Biztosan törlöd ezt a csapatot? Nem lesz visszavonási lehetőséged!", "delete-org-confirm-popup": "Biztosan törlöd ezt a szervezetet? Nem lesz visszavonási lehetőséged!", @@ -1013,7 +967,6 @@ "view-all": "Összes megtekintése", "filter-by-unread": "Olvasatlanokra szűkít", "mark-all-as-read": "Összes megjelölése olvasottként", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Minden olvasott eltávolítása", "allow-rename": "Átnevezés engedélyezése", "allowRenamePopup-title": "Átnevezés engedélyezése", @@ -1048,10 +1001,6 @@ "person": "Személy", "my-cards": "Kártyáim", "card": "Kártya", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Lista", "board": "Tábla", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Idő", - "cron-error-message": "Error Message", - "cron-error-details": "Részletek", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Kezdés", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Állapot", - "migration-progress-details": "Részletek", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Kész", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Megerősítés", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/hy.i18n.json b/imports/i18n/data/hy.i18n.json index e60953722..78f216ac6 100644 --- a/imports/i18n/data/hy.i18n.json +++ b/imports/i18n/data/hy.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/id.i18n.json b/imports/i18n/data/id.i18n.json index bc393a4b9..e5646003c 100644 --- a/imports/i18n/data/id.i18n.json +++ b/imports/i18n/data/id.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "komentar dihapus %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Klise", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Tambah Lampiran", @@ -98,7 +86,6 @@ "add-card": "Tambah Kartu", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Tambah Daftar", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Tambah setelah daftar", "add-members": "Tambah anggota", "added": "Ditambahkan", - "addMemberPopup-title": "Tambah anggota", + "addMemberPopup-title": "Daftar Anggota", "memberPopup-title": "Setelan Anggota", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Bisa tampilkan dan sunting kartu, menghapus partisipan, dan merubah setting panel", "admin-announcement": "Pengumuman", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Latar belakang gambar URL", "add-background-image": "Tambah latar belakang gambar", "remove-background-image": "Hapus latar belakang gambar", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s bintang", "board-not-found": "Panel tidak ditemukan", "board-private-info": "Panel ini akan jadi <strong>Pribadi<strong>", @@ -286,8 +269,6 @@ "change-permissions": "Ubah hak akses", "change-settings": "Ubah Setelan", "changeAvatarPopup-title": "Ubah Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Ubah Bahasa", "changePasswordPopup-title": "Ubah Kata Sandi", "changePermissionsPopup-title": "Ubah Hak Akses", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Hanya komentar", "comment-only-desc": "Bisa komen hanya di kartu", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Komputer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Buat", "createBoardPopup-title": "Buat Panel", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Buat Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Tanggal", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Tolak", "default-avatar": "Avatar standar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Daftar", "swimlanes": "Swimlanes", - "calendar": "Kalender", - "gantt": "Gantt", "log-out": "Keluar", "log-in": "Masuk", "loginPopup-title": "Masuk", "memberMenuPopup-title": "Setelan Anggota", - "grey-icons": "Grey Icons", "members": "Daftar Anggota", "menu": "Menu", "move-selection": "Pindahkan yang dipilih", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Pindahkan ke bawah", "moveCardToTop-title": "Pindahkan ke atas", "moveSelectionPopup-title": "Pindahkan yang dipilih", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi Pilihan", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "Tidak ada hasil", "normal": "Normal", "normal-desc": "Bisa tampilkan dan edit kartu. Tidak bisa ubah setting", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Undangan belum diterima", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Terima update dari semua panel, daftar atau kartu yang anda amati", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Dibuat pada", "modifiedAt": "Modified at", "verified": "Terverifikasi", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Ubah tanggal diterima", "editCardEndDatePopup-title": "Ubah tanggal berakhir", "setCardColorPopup-title": "Tetapkan warna", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Pilih warna", "setSwimlaneColorPopup-title": "Pilih warna", "setListColorPopup-title": "Pilih warna", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Hapus Papan?", "delete-board": "Hapus Papan", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Metode Autentikasi", "authentication-type": "Tipe Autentikasi", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Tata Letak", "hide-logo": "Sembunyikan Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "Lihat Semua", "filter-by-unread": "Saring yang Belum Dibaca", "mark-all-as-read": "Tandai semua telah dibaca", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Hapus semua yang telah dibaca", "allow-rename": "Ijinkan Ganti Nama", "allowRenamePopup-title": "Ijinkan Ganti Nama", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Waktu", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Mulai", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ig.i18n.json b/imports/i18n/data/ig.i18n.json index 6d9bd60d2..38e4d0620 100644 --- a/imports/i18n/data/ig.i18n.json +++ b/imports/i18n/data/ig.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Tinye ndị otu ọhụrụ", "added": "Etinyere", - "addMemberPopup-title": "Tinye ndị otu ọhụrụ", + "addMemberPopup-title": "Ndị otu", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Họrọ asụsụ ọzọ", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Ndị otu", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Ekere na", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Bido", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/it.i18n.json b/imports/i18n/data/it.i18n.json index eb26cd385..a6046dd35 100644 --- a/imports/i18n/data/it.i18n.json +++ b/imports/i18n/data/it.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "commento eliminato %s", "activity-receivedDate": "ha modificato la data di ricevuta a %s di %s", "activity-startDate": "ha modificato la data di inizio a %s di %s", - "allboards.starred": "Starred", - "allboards.templates": "Template", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "ha modificato la data di scadenza a %s di %s", "activity-endDate": "ha modificato la data di fine a %s di %s", "add-attachment": "Aggiungi allegato", @@ -98,7 +86,6 @@ "add-card": "Aggiungi scheda", "add-card-to-top-of-list": "Aggiungi Scheda in cima alla Lista", "add-card-to-bottom-of-list": "Aggiungi Scheda in fondo alla Lista", - "addListPopup-title": "Aggiungi lista", "setListWidthPopup-title": "Imposta larghezze", "set-list-width": "Imposta larghezze", "set-list-width-value": "Imposta larghezza min & max (in pixel)", @@ -122,10 +109,10 @@ "add-after-list": "Aggiungi Dopo la Lista", "add-members": "Aggiungi membri", "added": "Aggiunto/a", - "addMemberPopup-title": "Aggiungi membri", + "addMemberPopup-title": "Membri", "memberPopup-title": "Impostazioni membri", "admin": "Amministratore", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Può vedere e modificare schede, rimuovere membri e modificare le impostazioni della bacheca.", "admin-announcement": "Annunci", "admin-announcement-active": "Attiva annunci di sistema", "admin-announcement-title": "Annunci dell'Amministratore", @@ -167,16 +154,12 @@ "board-background-image-url": "URL dell'immagine di sfondo", "add-background-image": "Aggiungere immagine di sfondo", "remove-background-image": "Rimuovere immagine di sfondo", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "Impostazioni di tutte le bacheche", - "boardInfoOnMyBoardsPopup-title": "Impostazioni di tutte le bacheche", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "Impostazioni di tutte le bacheche", + "boardInfoOnMyBoardsPopup-title" : "Impostazioni di tutte le bacheche", "boardInfoOnMyBoards-title": "Impostazioni di tutte le bacheche", "show-card-counter-per-list": "Mostra numero schede per lista", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stelle", "board-not-found": "Bacheca non trovata", "board-private-info": "Questa bacheca sarà <strong>privata</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Modifica permessi", "change-settings": "Modifica impostazioni", "changeAvatarPopup-title": "Cambia avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Cambia lingua", "changePasswordPopup-title": "Modifica password", "changePermissionsPopup-title": "Modifica permessi", @@ -335,16 +316,10 @@ "comment-placeholder": "Scrivi un commento...", "comment-only": "Solo commenti", "comment-only-desc": "Puoi commentare solo le schede.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Sei sicuro di voler cancellare il commento?", "deleteCommentPopup-title": "Eliminare commento?", "no-comments": "Nessun commento", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Impossibile visualizzare commenti o attività.", "worker": "Lavoratore", "worker-desc": "Può solo spostare schede, assegnarsi una scheda e commentare.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Sicuro di voler cancellare la checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Eliminare checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copia link della scheda negli appunti", "copy-text-to-clipboard": "Copia il testo negli appunti", "linkCardPopup-title": "Collega scheda", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Titolo prima scheda\", \"description\":\"Descrizione prima scheda\"}, {\"title\":\"Titolo seconda scheda\",\"description\":\"Descrizione seconda scheda\"},{\"title\":\"Titolo ultima scheda\",\"description\":\"Descrizione ultima scheda\"} ]", "create": "Crea", "createBoardPopup-title": "Crea bacheca", - "createTemplateContainerPopup-title": "Aggiungi contenitore di Template", "chooseBoardSourcePopup-title": "Importa bacheca", "createLabelPopup-title": "Crea etichetta", "createCustomField": "Crea campo", @@ -385,7 +358,7 @@ "date": "Data", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Rifiuta", "default-avatar": "Avatar predefinito", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Puoi spostare un elenco nell'archivio per rimuoverlo dalla bacheca e mantentere la sua attività.", "lists": "Liste", "swimlanes": "Swimlane", - "calendar": "Calendario", - "gantt": "Gantt", "log-out": "Esci", "log-in": "Accedi", "loginPopup-title": "Accedi", "memberMenuPopup-title": "Impostazioni membri", - "grey-icons": "Grey Icons", "members": "Membri", "menu": "Menu", "move-selection": "Sposta elementi selezionati", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Sposta in fondo", "moveCardToTop-title": "Sposta in cima", "moveSelectionPopup-title": "Sposta elementi selezionati", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Selezione multipla", "multi-selection-label": "Selezionare etichetta", "multi-selection-member": "Selezionare membro", @@ -587,8 +555,6 @@ "no-results": "Nessun risultato", "normal": "Normale", "normal-desc": "Può visionare e modificare le schede. Non può cambiare le impostazioni.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitato non ancora accettato", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Ricevi aggiornamenti per tutte le bacheche, liste o schede che stai seguendo", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Permetti modifica dell'email", "accounts-allowUserNameChange": "Consenti la modifica del nome utente", "tableVisibilityMode-allowPrivateOnly": "Visibilità bacheche: Consenti solo le bacheche private", - "tableVisibilityMode": "Visibilità bacheche", + "tableVisibilityMode" : "Visibilità bacheche", "createdAt": "Creato alle", "modifiedAt": "Modificato il", "verified": "Verificato", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Cambia data ricezione", "editCardEndDatePopup-title": "Cambia data finale", "setCardColorPopup-title": "Imposta il colore", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Scegli un colore", "setSwimlaneColorPopup-title": "Scegli un colore", "setListColorPopup-title": "Scegli un colore", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Tutte le liste, schede, etichette e azioni saranno rimosse e non sarai più in grado di recuperare il contenuto della bacheca. L'azione non è annullabile.", "boardDeletePopup-title": "Eliminare la bacheca?", "delete-board": "Elimina bacheca", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Sottocompiti per la bacheca __board__", @@ -942,13 +905,6 @@ "authentication-method": "Metodo di autenticazione", "authentication-type": "Tipo di autenticazione", "custom-product-name": "Nome prodotto personalizzato", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Nascondi il logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "orario finale modificato in", "a-startAt": "orario iniziale modificato in", "a-receivedAt": "orario di ricezione modificato in", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "la data di scadenza attuale %s si sta avvicinando", "pastdue": "la data di scadenza attuale %s è scaduta", "duenow": "la data di scadenza attuale %s è oggi", @@ -989,7 +943,7 @@ "act-almostdue": "sollecito inviato: la scadenza (__timeValue__) di __card__ è vicina", "act-pastdue": "sollecito inviato: la scadenza (__timeValue__) di __card__ è già passata", "act-duenow": "sollecito inviato: la scadenza (__timeValue__) di __card__ è adesso", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Sei stato menzionato in [__board__] __list__/__card__", "delete-user-confirm-popup": "Sei sicuro di voler eliminare questo profilo? Non sarà possibile ripristinarlo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "Mostra tutte", "filter-by-unread": "Filtra per non letto", "mark-all-as-read": "Segna tutto come letto", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Rimuovi tutti i già letti", "allow-rename": "Consenti Rinomina", "allowRenamePopup-title": "Consenti Rinomina", @@ -1048,10 +1001,6 @@ "person": "Persona", "my-cards": "Le mie schede", "card": "Scheda", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Lista", "board": "Bacheca", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Nascondi gli elementi della lista di controllo", "support": "Supporto", "supportPopup-title": "Supporto", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibilità", "accessibility-page-enabled": "Pagina accessibilità abilitata", "accessibility-info-not-added-yet": "Le informazioni sull'accessibilità non sono ancora state aggiunte ", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Ora", - "cron-error-message": "Error Message", - "cron-error-details": "Dettagli", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Completato", - "idle": "Inattivo", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Inizio", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Completato", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Stato", - "migration-progress-details": "Dettagli", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completato/a", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Peso", - "cron": "Pianificazione", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Conferma", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Inattivo", + "complete": "Completato", + "cron": "Pianificazione" } diff --git a/imports/i18n/data/ja-HI.i18n.json b/imports/i18n/data/ja-HI.i18n.json index 0baabd781..db428e93a 100644 --- a/imports/i18n/data/ja-HI.i18n.json +++ b/imports/i18n/data/ja-HI.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "幅を設定", "set-list-width": "幅を設定", "set-list-width-value": "最小幅と最大幅を設定(pixel)", @@ -122,10 +109,10 @@ "add-after-list": "リストの最後に追加", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "背景画像のURL", "add-background-image": "背景画像を追加", "remove-background-image": "背景画像を削除", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "すべてのチェックリスト項目を非表示", "support": "サポート", "supportPopup-title": "サポート", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "アクセシビリティ", "accessibility-page-enabled": "アクセシビリティページが有効", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "リストをリストア", - "step-restore-cards": "カードをリストア", - "step-restore-swimlanes": "スイムレーンをリストア", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ja.i18n.json b/imports/i18n/data/ja.i18n.json index 00b07b65e..3f6a29f13 100644 --- a/imports/i18n/data/ja.i18n.json +++ b/imports/i18n/data/ja.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "コメント %s を削除しました", "activity-receivedDate": "受付日を %s に変更しました / %s", "activity-startDate": "開始日を %s に変更しました / %s", - "allboards.starred": "スター", - "allboards.templates": "テンプレート", - "allboards.remaining": "残り", - "allboards.workspaces": "ワークスペース", - "allboards.add-workspace": "ワークスペースを追加", - "allboards.add-workspace-prompt": "ワークスペース名", - "allboards.add-subworkspace": "サブワークスペースを追加", - "allboards.add-subworkspace-prompt": "サブワークスペース名", - "allboards.edit-workspace": "ワークスペースを編集", - "allboards.edit-workspace-name": "ワークスペース名", - "allboards.edit-workspace-icon": "ワークスペースアイコン(マークダウン)", - "multi-selection-active": "チェックボックスをクリックしてボードを選択", "activity-dueDate": "期限日を %s に変更しました / %s", "activity-endDate": "終了日を %s に変更しました / %s", "add-attachment": "添付ファイルを追加", @@ -98,7 +86,6 @@ "add-card": "カードを追加", "add-card-to-top-of-list": "カードをリストの先頭に追加", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "リストを追加", "setListWidthPopup-title": "幅を設定", "set-list-width": "幅を設定", "set-list-width-value": "最小幅と最大幅を設定(pixel)", @@ -122,10 +109,10 @@ "add-after-list": "リストの最後に追加", "add-members": "メンバーの追加", "added": "追加しました", - "addMemberPopup-title": "メンバーの追加", + "addMemberPopup-title": "メンバー", "memberPopup-title": "メンバー設定", "admin": "管理", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "カードの閲覧と編集、メンバーの削除、ボードの設定変更が可能", "admin-announcement": "アナウンス", "admin-announcement-active": "システム全体アナウンスを有効化", "admin-announcement-title": "管理者からのアナウンス", @@ -167,16 +154,12 @@ "board-background-image-url": "背景画像のURL", "add-background-image": "背景画像を追加", "remove-background-image": "背景画像を削除", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "すべてのボードメンバ", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "ボードが見つかりません", "board-private-info": "ボードは <strong>非公開</strong> になります。", @@ -208,8 +191,8 @@ "board-view-gantt": "ガント", "board-view-lists": "リスト", "bucket-example": "Like \"Bucket List\" for example", - "calendar-previous-month-label": "前月", - "calendar-next-month-label": "次月", + "calendar-previous-month-label": "Previous Month", + "calendar-next-month-label": "Next Month", "cancel": "キャンセル", "card-archived": "このカードをアーカイブしました。", "board-archived": "このボードをアーカイブしました。", @@ -286,8 +269,6 @@ "change-permissions": "権限の変更", "change-settings": "設定の変更", "changeAvatarPopup-title": "アバターの変更", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "アバターを削除しますか?", "changeLanguagePopup-title": "言語の変更", "changePasswordPopup-title": "パスワードの変更", "changePermissionsPopup-title": "パーミッションの変更", @@ -335,16 +316,10 @@ "comment-placeholder": "コメントを書く", "comment-only": "コメントのみ", "comment-only-desc": "カードにのみコメント可能", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "コメントを削除してもよろしいでしょうか?", "deleteCommentPopup-title": "コメントを削除しますか?", "no-comments": "コメントなし", - "no-comments-desc": "コメントの閲覧不可", - "read-only": "読み取り専用", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "コメントとアクティビティの閲覧不可。", "worker": "作業者", "worker-desc": "カードの移動、自分への割り当て、コメントが可能。", "computer": "コンピューター", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "チェックリストを削除してもよろしいでしょうか?", "subtaskDeletePopup-title": "サブタスクを削除しますか?", "checklistDeletePopup-title": "チェックリストを削除しますか?", - "checklistItemDeletePopup-title": "チェックリスト項目を削除しますか?", "copy-card-link-to-clipboard": "カードへのリンクをクリップボードにコピー", "copy-text-to-clipboard": "テキストをクリップボードにコピー", "linkCardPopup-title": "カードをリンク", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"1つ目のカードタイトル\", \"description\":\"1つ目のカードの説明\"}, {\"title\":\"2つ目のカードタイトル\",\"description\":\"2つ目のカードの説明\"},{\"title\":\"最後のカードタイトル\",\"description\":\"最後のカードの説明\"} ]", "create": "作成", "createBoardPopup-title": "ボードの作成", - "createTemplateContainerPopup-title": "テンプレートコンテナを追加", "chooseBoardSourcePopup-title": "ボードをインポート", "createLabelPopup-title": "ラベルの作成", "createCustomField": "フィールドを作成", @@ -383,10 +356,10 @@ "custom-field-text": "テキスト", "custom-fields": "カスタムフィールド", "date": "日付", - "date-format": "日付形式", - "date-format-yyyy-mm-dd": "年-月-日", - "date-format-dd-mm-yyyy": "日-月-年", - "date-format-mm-dd-yyyy": "月-日-年", + "date-format": "Date Format", + "date-format-yyyy-mm-dd": "YYYY-MM-DD", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "拒否", "default-avatar": "デフォルトのアバター", "delete": "削除", @@ -412,7 +385,7 @@ "editNotificationPopup-title": "通知の変更", "editProfilePopup-title": "プロフィールの編集", "email": "メールアドレス", - "email-address": "メールアドレス", + "email-address": "Email Address", "email-enrollAccount-subject": "__siteName__であなたのアカウントが作成されました", "email-enrollAccount-text": "こんにちは、__user__さん。\n\nサービスを開始するには、以下をクリックしてください。\n\n__url__\n\nよろしくお願いします。", "email-fail": "メールの送信に失敗しました", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "リストをアーカイブするとボードから削除され、アクティビティに保持されます。", "lists": "リスト", "swimlanes": "スイムレーン", - "calendar": "カレンダー", - "gantt": "ガント", "log-out": "ログアウト", "log-in": "ログイン", "loginPopup-title": "ログイン", "memberMenuPopup-title": "メンバー設定", - "grey-icons": "グレイアイコン", "members": "メンバー", "menu": "メニュー", "move-selection": "選択したものを移動", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "最下部に移動", "moveCardToTop-title": "先頭に移動", "moveSelectionPopup-title": "選択したものを移動", - "copySelectionPopup-title": "選択範囲をコピー", - "selection-color": "色を選択", "multi-selection": "複数選択", "multi-selection-label": "選択したものにラベルを設定", "multi-selection-member": "選択したものにメンバーを設定", @@ -587,8 +555,6 @@ "no-results": "該当するものはありません", "normal": "通常", "normal-desc": "カードの閲覧と編集が可能。設定変更不可。", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "招待はアクセプトされていません", "notify-participate": "作成した、またはメンバーとなったカードの更新情報を受け取る", "notify-watch": "ウォッチしているすべてのボード、リスト、カードの更新情報を受け取る", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "メールアドレスの変更を許可", "accounts-allowUserNameChange": "ユーザー名の変更を許可", "tableVisibilityMode-allowPrivateOnly": "ボードの公開設定:プライベートのボードのみを許可", - "tableVisibilityMode": "ボードの公開設定", + "tableVisibilityMode" : "ボードの公開設定", "createdAt": "作成日時", "modifiedAt": "更新日時", "verified": "認証状況", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "受付日の変更", "editCardEndDatePopup-title": "終了日の変更", "setCardColorPopup-title": "色を選択", - "setSelectionColorPopup-title": "選択した色を設定", "setCardActionsColorPopup-title": "色を選択", "setSwimlaneColorPopup-title": "色を選択", "setListColorPopup-title": "色を選択", @@ -790,9 +755,7 @@ "delete-board-confirm-popup": "すべてのリスト、カード、ラベル、アクティビティは削除され、ボードの内容を元に戻すことができません。", "boardDeletePopup-title": "ボードを削除しますか?", "delete-board": "ボードを削除", - "delete-all-notifications": "すべての通知を削除", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", - "delete-duplicate-lists": "重複リストを削除", + "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "__board__ ボードのサブタスク", "default": "デフォルト", @@ -942,13 +905,6 @@ "authentication-method": "認証方式", "authentication-type": "認証タイプ", "custom-product-name": "カスタム製品名", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "レイアウト", "hide-logo": "ロゴを隠す", "hide-card-counter-list": "すべてのボードからカードカウンタリストを非表示にする。", @@ -979,8 +935,6 @@ "a-endAt": "終了を変更しました", "a-startAt": "開始を変更しました", "a-receivedAt": "受付を変更しました", - "above-selected-card": "選択したカードを上へ", - "below-selected-card": "選択したカードを下へ", "almostdue": "期限 %s が近づいています", "pastdue": "期限 %s が過ぎています", "duenow": "期限 %s は本日です", @@ -989,7 +943,7 @@ "act-almostdue": "__card__ の期限日時 (__timeValue__) が近づいています", "act-pastdue": "__card__ の期限日時 (__timeValue__) が過ぎています", "act-duenow": "__card__ の期限日時 (__timeValue__) になりました", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "あなたが [__board__] __list__/__card__ に追記しました", "delete-user-confirm-popup": "本当にこのアカウントを削除しますか?削除すると元に戻すことはできません。", "delete-team-confirm-popup": "本当にこのチームを削除しますか?削除すると元に戻すことはできません。", "delete-org-confirm-popup": "本当にこの組織を削除しますか?削除すると元に戻すことはできません。", @@ -1013,7 +967,6 @@ "view-all": "全てを表示", "filter-by-unread": "未読でフィルタ", "mark-all-as-read": "全て既読にする", - "mark-all-as-unread": "すべての未読をマーク", "remove-all-read": "全ての既読を削除", "allow-rename": "リネームを許可する", "allowRenamePopup-title": "リネームを許可する", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "自分のカード", "card": "カード", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "リスト", "board": "ボード", "context-separator": "/", @@ -1069,7 +1018,7 @@ "dueCardsViewChange-choice-me": "自分", "dueCardsViewChange-choice-all": "全ユーザー", "dueCardsViewChange-choice-all-description": "ユーザーに権限のあるボードから、期限が切れたすべての未完了のカードを表示します。", - "dueCards-noResults-title": "期限切れのカードはありません", + "dueCards-noResults-title": "No Due Cards Found", "dueCards-noResults-description": "You don't have any cards with due dates at the moment.", "broken-cards": "壊れたカード", "board-title-not-found": "ボード「%s」は見つかりませんでした。", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "すべてのチェックリスト項目を非表示", "support": "サポート", "supportPopup-title": "サポート", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "サポートタイトル", - "support-content": "Support content", "accessibility": "アクセシビリティ", "accessibility-page-enabled": "アクセシビリティページが有効", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "時間", - "cron-error-message": "Error Message", - "cron-error-details": "詳細", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "完了", - "idle": "アイドル", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "開始", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "重複した空リストを削除", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "復元されたアイテム", - "restore-lost-cards-migration": "紛失カードを復元", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "全アーカイブを復元", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "全ファイルのURLを修正", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "完了", - "migration-running": "実行中...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "マイグレーション", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "問題は見つかりませんでした", - "run-migration": "移行を実行", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "ステータス", - "migration-progress-details": "詳細", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "手順", - "view": "ビュー", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "重複した空のリストを削除", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "リストをリストア", - "step-restore-cards": "カードをリストア", - "step-restore-swimlanes": "スイムレーンをリストア", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "完了した時", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "重み", - "cron": "スケジュール", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "確認", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "アイドル", + "complete": "完了", + "cron": "スケジュール" } diff --git a/imports/i18n/data/ka.i18n.json b/imports/i18n/data/ka.i18n.json index c8b49f5a1..0e41f2671 100644 --- a/imports/i18n/data/ka.i18n.json +++ b/imports/i18n/data/ka.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "მიბმული ფაილის დამატება", @@ -98,7 +86,6 @@ "add-card": "ბარათის დამატება", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "ჩამონათვალის დამატება", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "წევრების დამატება", "added": "-მა დაამატა", - "addMemberPopup-title": "წევრების დამატება", + "addMemberPopup-title": "წევრები", "memberPopup-title": "მომხმარებლის პარამეტრები", "admin": "ადმინი", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "შეუძლია ნახოს და შეასწოროს ბარათები, წაშალოს წევრები და შეცვალოს დაფის პარამეტრები.", "admin-announcement": "განცხადება", "admin-announcement-active": "აქტიური სისტემა-ფართო განცხადება", "admin-announcement-title": "შეტყობინება ადმინისტრატორისთვის", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s ვარსკვლავი", "board-not-found": "დაფა არ მოიძებნა", "board-private-info": "ეს დაფა იქნება <strong>პირადი</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "პარამეტრების შეცვლა", "change-settings": "პარამეტრების შეცვლა", "changeAvatarPopup-title": "სურათის შეცვლა", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "ენის შეცვლა", "changePasswordPopup-title": "პაროლის შეცვლა", "changePermissionsPopup-title": "უფლებების შეცვლა", @@ -335,16 +316,10 @@ "comment-placeholder": "დაწერეთ კომენტარი", "comment-only": "მხოლოდ კომენტარები", "comment-only-desc": "თქვენ შეგიძლიათ კომენტარის გაკეთება მხოლოდ ბარათებზე.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "კომპიუტერი", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "დააკოპირეთ ბარათის ბმული clipboard-ზე", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"სათაური\": \"პირველი ბარათის სათაური\", \"აღწერა\":\"პირველი ბარათის აღწერა\"}, {\"სათაური\":\"მეორე ბარათის სათაური\",\"აღწერა\":\"მეორე ბარათის აღწერა\"},{\"სათაური\":\"ბოლო ბარათის სათაური\",\"აღწერა\":\"ბოლო ბარათის აღწერა\"} ]", "create": "შექმნა", "createBoardPopup-title": "დაფის შექმნა", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "დაფის იმპორტი", "createLabelPopup-title": "ნიშნის შექმნა", "createCustomField": "ველის შექმნა", @@ -385,7 +358,7 @@ "date": "თარიღი", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "უარყოფა", "default-avatar": "სტანდარტული ავატარი", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "ჩამონათვალი", "swimlanes": "ბილიკები", - "calendar": "კალენდარი", - "gantt": "Gantt", "log-out": "გამოსვლა", "log-in": "შესვლა", "loginPopup-title": "შესვლა", "memberMenuPopup-title": "მომხმარებლის პარამეტრები", - "grey-icons": "Grey Icons", "members": "წევრები", "menu": "მენიუ", "move-selection": "მონიშნულის მოძრაობა", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "ქვევით ჩამოწევა", "moveCardToTop-title": "ზევით აწევა", "moveSelectionPopup-title": "მონიშნულის მოძრაობა", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "რამდენიმეს მონიშვნა", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "შედეგის გარეშე", "normal": "ნორმალური", "normal-desc": "შეუძლია ნახოს და შეასწოროს ბარათები. ამ პარამეტრების შეცვლა შეუძლებელია.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "მოწვევა ჯერ არ დადასტურებულა", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "მიიღეთ განახლებები ყველა დაფაზე, ჩამონათვალზე ან ბარათებზე, რომელსაც თქვენ აკვირდებით", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "ელ.ფოსტის ცვლილების უფლების დაშვება", "accounts-allowUserNameChange": "მომხმარებლის სახელის ცვლილების უფლების დაშვება", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "შექმნილია", "modifiedAt": "Modified at", "verified": "შემოწმებული", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "შეცვალეთ საბოლოო თარიღი", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "ყველა ჩამონათვალი, ბარათი, ნიშანი და აქტივობა წაიშლება და თქვენ ვეღარ შეძლებთ მის აღდგენას.", "boardDeletePopup-title": "წავშალოთ დაფა?", "delete-board": "დაფის წაშლა", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "ქვესაქმიანობა __board__ დაფისთვის", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "დრო", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "დაწყება", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/km.i18n.json b/imports/i18n/data/km.i18n.json index ab3f3cf4e..99d757f8b 100644 --- a/imports/i18n/data/km.i18n.json +++ b/imports/i18n/data/km.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/km_KH.i18n.json b/imports/i18n/data/km_KH.i18n.json deleted file mode 100644 index d9ef3e7d3..000000000 --- a/imports/i18n/data/km_KH.i18n.json +++ /dev/null @@ -1,1686 +0,0 @@ -{ - "accept": "Accept", - "act-activity-notify": "Activity Notification", - "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createBoard": "created board __board__", - "act-createSwimlane": "created swimlane __swimlane__ to board __board__", - "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-createCustomField": "created custom field __customField__ at board __board__", - "act-deleteCustomField": "deleted custom field __customField__ at board __board__", - "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createList": "added list __list__ to board __board__", - "act-addBoardMember": "added member __member__ to board __board__", - "act-archivedBoard": "Board __board__ moved to Archive", - "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", - "act-importBoard": "imported board __board__", - "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", - "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__", - "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-removeBoardMember": "removed member __member__ from board __board__", - "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-withBoardTitle": "__board__", - "act-withCardTitle": "[__board__] __card__", - "actions": "Actions", - "activities": "Activities", - "activity": "Activity", - "activity-added": "added %s to %s", - "activity-archived": "%s moved to Archive", - "activity-attached": "attached %s to %s", - "activity-created": "created %s", - "activity-changedListTitle": "renamed list to %s", - "activity-customfield-created": "created custom field %s", - "activity-excluded": "excluded %s from %s", - "activity-imported": "imported %s into %s from %s", - "activity-imported-board": "imported %s from %s", - "activity-joined": "joined %s", - "activity-moved": "moved %s from %s to %s", - "activity-on": "on %s", - "activity-removed": "removed %s from %s", - "activity-sent": "sent %s to %s", - "activity-unjoined": "unjoined %s", - "activity-subtask-added": "added subtask to %s", - "activity-checked-item": "checked %s in checklist %s of %s", - "activity-unchecked-item": "unchecked %s in checklist %s of %s", - "activity-checklist-added": "added checklist to %s", - "activity-checklist-removed": "removed a checklist from %s", - "activity-checklist-completed": "completed checklist %s of %s", - "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", - "activity-checklist-item-added": "added checklist item to '%s' in %s", - "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", - "add": "Add", - "activity-checked-item-card": "checked %s in checklist %s", - "activity-unchecked-item-card": "unchecked %s in checklist %s", - "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "activity-checklist-uncompleted-card": "uncompleted the checklist %s", - "activity-editComment": "edited comment %s", - "activity-deleteComment": "deleted comment %s", - "activity-receivedDate": "edited received date to %s of %s", - "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", - "activity-dueDate": "edited due date to %s of %s", - "activity-endDate": "edited end date to %s of %s", - "add-attachment": "Add Attachment", - "add-board": "Add Board", - "add-template": "Add Template", - "add-card": "Add Card", - "add-card-to-top-of-list": "Add Card to Top of List", - "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", - "setListWidthPopup-title": "Set Widths", - "set-list-width": "Set Widths", - "set-list-width-value": "Set Min & Max Widths (pixels)", - "list-width-error-message": "List widths must be integers greater than 100", - "keyboard-shortcuts-enabled": "Keyboard shortcuts enabled. Click to disable.", - "keyboard-shortcuts-disabled": "Keyboard shortcuts disabled. Click to enable.", - "setSwimlaneHeightPopup-title": "Set Swimlane Height", - "set-swimlane-height": "Set Swimlane Height", - "set-swimlane-height-value": "Swimlane Height (pixels)", - "swimlane-height-error-message": "Swimlane height must be a positive integer", - "add-swimlane": "Add Swimlane", - "add-subtask": "Add Subtask", - "add-checklist": "Add Checklist", - "add-checklist-item": "Add an item to checklist", - "close-add-checklist-item": "Close add an item to checklist form", - "close-edit-checklist-item": "Close edit an item to checklist form", - "convertChecklistItemToCardPopup-title": "Convert to Card", - "add-cover": "Add cover image to minicard", - "add-label": "Add Label", - "add-list": "Add List", - "add-after-list": "Add After List", - "add-members": "Add Members", - "added": "Added", - "addMemberPopup-title": "Add Members", - "memberPopup-title": "Member Settings", - "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", - "admin-announcement": "Announcement", - "admin-announcement-active": "Active System-Wide Announcement", - "admin-announcement-title": "Announcement from Administrator", - "all-boards": "All Boards", - "and-n-other-card": "And __count__ other card", - "and-n-other-card_plural": "And __count__ other cards", - "apply": "Apply", - "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", - "app-try-reconnect": "Try to reconnect.", - "archive": "Move to Archive", - "archive-all": "Move All to Archive", - "archive-board": "Move Board to Archive", - "archive-board-confirm": "Are you sure you want to archive this board?", - "archive-card": "Move Card to Archive", - "archive-list": "Move List to Archive", - "archive-swimlane": "Move Swimlane to Archive", - "archive-selection": "Move selection to Archive", - "archiveBoardPopup-title": "Move Board to Archive?", - "archived-items": "Archive", - "archived-boards": "Boards in Archive", - "restore-board": "Restore Board", - "no-archived-boards": "No Boards in Archive.", - "archives": "Archive", - "template": "Template", - "templates": "Templates", - "template-container": "Template Container", - "add-template-container": "Add Template Container", - "assign-member": "Assign member", - "attached": "attached", - "attachment": "Attachment", - "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.", - "attachmentDeletePopup-title": "Delete Attachment?", - "attachments": "Attachments", - "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (__size__ max)", - "back": "Back", - "board-change-color": "Change color", - "board-change-background-image": "Change Background Image", - "board-background-image-url": "Background Image URL", - "add-background-image": "Add Background Image", - "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", - "boardInfoOnMyBoards-title": "All Boards Settings", - "show-card-counter-per-list": "Show card count per list", - "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", - "board-nb-stars": "%s stars", - "board-not-found": "Board not found", - "board-private-info": "This board will be <strong>private</strong>.", - "board-public-info": "This board will be <strong>public</strong>.", - "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", - "boardChangeColorPopup-title": "Change Board Background", - "boardChangeBackgroundImagePopup-title": "Change Background Image", - "allBoardsChangeColorPopup-title": "Change color", - "allBoardsChangeBackgroundImagePopup-title": "Change Background Image", - "boardChangeTitlePopup-title": "Rename Board", - "boardChangeVisibilityPopup-title": "Change Visibility", - "boardChangeWatchPopup-title": "Change Watch", - "boardMenuPopup-title": "Board Settings", - "allBoardsMenuPopup-title": "Settings", - "boardChangeViewPopup-title": "Board View", - "boards": "Boards", - "board-view": "Board View", - "desktop-mode": "Desktop Mode", - "mobile-mode": "Mobile Mode", - "mobile-desktop-toggle": "Toggle between Mobile and Desktop Mode", - "zoom-in": "Zoom In", - "zoom-out": "Zoom Out", - "click-to-change-zoom": "Click to change zoom level", - "zoom-level": "Zoom Level", - "enter-zoom-level": "Enter zoom level (50-300%):", - "board-view-cal": "Calendar", - "board-view-swimlanes": "Swimlanes", - "board-view-collapse": "Collapse", - "board-view-gantt": "Gantt", - "board-view-lists": "Lists", - "bucket-example": "Like \"Bucket List\" for example", - "calendar-previous-month-label": "Previous Month", - "calendar-next-month-label": "Next Month", - "cancel": "Cancel", - "card-archived": "This card is moved to Archive.", - "board-archived": "This board is moved to Archive.", - "card-comments-title": "This card has %s comment.", - "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.", - "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", - "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", - "card-archive-pop": "Card will not be visible at this list after archiving card.", - "card-archive-suggest-cancel": "You can later restore card from Archive.", - "card-due": "Due", - "card-due-on": "Due on", - "card-spent": "Spent Time", - "card-edit-attachments": "Edit attachments", - "card-edit-custom-fields": "Edit custom fields", - "card-edit-labels": "Edit labels", - "card-edit-members": "Edit members", - "card-labels-title": "Change the labels for the card.", - "card-members-title": "Add or remove members of the board from the card.", - "card-start": "Start", - "card-start-on": "Starts on", - "cardAttachmentsPopup-title": "Attach From", - "cardCustomField-datePopup-title": "Change date", - "cardCustomFieldsPopup-title": "Edit custom fields", - "cardStartVotingPopup-title": "Start a vote", - "positiveVoteMembersPopup-title": "Proponents", - "negativeVoteMembersPopup-title": "Opponents", - "card-edit-voting": "Edit voting", - "editVoteEndDatePopup-title": "Change vote end date", - "allowNonBoardMembers": "Allow all logged in users", - "vote-question": "Voting question", - "vote-public": "Show who voted what", - "vote-for-it": "for it", - "vote-against": "against", - "deleteVotePopup-title": "Delete vote?", - "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", - "cardStartPlanningPokerPopup-title": "Start a Planning Poker", - "card-edit-planning-poker": "Edit Planning Poker", - "editPokerEndDatePopup-title": "Change Planning Poker vote end date", - "poker-question": "Planning Poker", - "poker-one": "1", - "poker-two": "2", - "poker-three": "3", - "poker-five": "5", - "poker-eight": "8", - "poker-thirteen": "13", - "poker-twenty": "20", - "poker-forty": "40", - "poker-oneHundred": "100", - "poker-unsure": "?", - "poker-finish": "Finish", - "poker-result-votes": "Votes", - "poker-result-who": "Who", - "poker-replay": "Replay", - "set-estimation": "Set Estimation", - "deletePokerPopup-title": "Delete planning poker?", - "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", - "cardDeletePopup-title": "Delete Card?", - "cardArchivePopup-title": "Archive Card?", - "cardDetailsActionsPopup-title": "Card Actions", - "cardLabelsPopup-title": "Labels", - "cardMembersPopup-title": "Members", - "cardMorePopup-title": "More", - "cardTemplatePopup-title": "Create template", - "cards": "Cards", - "cards-count": "Cards", - "cards-count-one": "Card", - "casSignIn": "Sign In with CAS", - "cardType-card": "Card", - "cardType-linkedCard": "Linked Card", - "cardType-linkedBoard": "Linked Board", - "change": "Change", - "change-avatar": "Change Avatar", - "change-password": "Change Password", - "change-permissions": "Change permissions", - "change-settings": "Change Settings", - "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", - "changeLanguagePopup-title": "Change Language", - "changePasswordPopup-title": "Change Password", - "changePermissionsPopup-title": "Change Permissions", - "changeSettingsPopup-title": "Change Settings", - "subtasks": "Subtasks", - "checklists": "Checklists", - "click-to-star": "Click to star this board.", - "click-to-unstar": "Click to unstar this board.", - "click-to-enable-auto-width": "Auto list width disabled. Click to enable.", - "click-to-disable-auto-width": "Auto list width enabled. Click to disable.", - "auto-list-width": "Auto list width", - "clipboard": "Clipboard or drag & drop", - "close": "Close", - "close-board": "Close Board", - "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", - "close-card": "Close Card", - "color-black": "black", - "color-blue": "blue", - "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", - "color-green": "green", - "color-indigo": "indigo", - "color-lime": "lime", - "color-magenta": "magenta", - "color-mistyrose": "mistyrose", - "color-navy": "navy", - "color-orange": "orange", - "color-paleturquoise": "paleturquoise", - "color-peachpuff": "peachpuff", - "color-pink": "pink", - "color-plum": "plum", - "color-purple": "purple", - "color-red": "red", - "color-saddlebrown": "saddlebrown", - "color-silver": "silver", - "color-sky": "sky", - "color-slateblue": "slateblue", - "color-white": "white", - "color-yellow": "yellow", - "unset-color": "Unset", - "comments": "Comments", - "comment": "Comment", - "comment-placeholder": "Write Comment", - "comment-only": "Comment only", - "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", - "comment-delete": "Are you sure you want to delete the comment?", - "deleteCommentPopup-title": "Delete comment?", - "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", - "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", - "computer": "Computer", - "confirm-subtask-delete-popup": "Are you sure you want to delete subtask?", - "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", - "subtaskDeletePopup-title": "Delete Subtask?", - "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", - "copy-card-link-to-clipboard": "Copy card link to clipboard", - "copy-text-to-clipboard": "Copy text to clipboard", - "linkCardPopup-title": "Link Card", - "searchElementPopup-title": "Search", - "copyCardPopup-title": "Copy Card", - "copyManyCardsPopup-title": "Copy Template to Many Cards", - "copyManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", - "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", - "create": "Create", - "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", - "chooseBoardSourcePopup-title": "Import board", - "createLabelPopup-title": "Create Label", - "createCustomField": "Create Field", - "createCustomFieldPopup-title": "Create Field", - "current": "current", - "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", - "custom-field-checkbox": "Checkbox", - "custom-field-currency": "Currency", - "custom-field-currency-option": "Currency Code", - "custom-field-date": "Date", - "custom-field-dropdown": "Dropdown List", - "custom-field-dropdown-none": "(none)", - "custom-field-dropdown-options": "List Options", - "custom-field-dropdown-options-placeholder": "Press enter to add more options", - "custom-field-dropdown-unknown": "(unknown)", - "custom-field-number": "Number", - "custom-field-text": "Text", - "custom-fields": "Custom Fields", - "date": "Date", - "date-format": "Date Format", - "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", - "date-format-mm-dd-yyyy": "MM-DD-YYYY", - "decline": "Decline", - "default-avatar": "Default avatar", - "delete": "Delete", - "deleteCustomFieldPopup-title": "Delete Custom Field?", - "deleteLabelPopup-title": "Delete Label?", - "description": "Description", - "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", - "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", - "discard": "Discard", - "done": "Done", - "download": "Download", - "edit": "Edit", - "edit-avatar": "Change Avatar", - "edit-profile": "Edit Profile", - "edit-wip-limit": "Edit WIP Limit", - "soft-wip-limit": "Soft WIP Limit", - "editCardStartDatePopup-title": "Change start date", - "editCardDueDatePopup-title": "Change due date", - "editCustomFieldPopup-title": "Edit Field", - "addReactionPopup-title": "Add reaction", - "editCardSpentTimePopup-title": "Change spent time", - "editLabelPopup-title": "Change Label", - "editNotificationPopup-title": "Edit Notification", - "editProfilePopup-title": "Edit Profile", - "email": "Email", - "email-address": "Email Address", - "email-enrollAccount-subject": "An account created for you on __siteName__", - "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", - "email-fail": "Sending email failed", - "email-fail-text": "Error trying to send email", - "email-invalid": "Invalid email", - "email-invite": "Invite via Email", - "email-invite-subject": "__inviter__ sent you an invitation", - "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", - "email-resetPassword-subject": "Reset your password on __siteName__", - "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", - "email-sent": "Email sent", - "email-verifyEmail-subject": "Verify your email address on __siteName__", - "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", - "enable-vertical-scrollbars": "Enable vertical scrollbars", - "enable-wip-limit": "Enable WIP Limit", - "error-board-doesNotExist": "This board does not exist", - "error-board-notAdmin": "You need to be admin of this board to do that", - "error-board-notAMember": "You need to be a member of this board to do that", - "error-json-malformed": "Your text is not valid JSON", - "error-json-schema": "Your JSON data does not include the proper information in the correct format", - "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format ", - "error-list-doesNotExist": "This list does not exist", - "error-user-doesNotExist": "This user does not exist", - "error-user-notAllowSelf": "You can not invite yourself", - "error-user-notCreated": "This user is not created", - "error-username-taken": "This username is already taken", - "error-orgname-taken": "This organization name is already taken", - "error-teamname-taken": "This team name is already taken", - "error-email-taken": "Email has already been taken", - "export-board": "Export board", - "export-board-json": "Export board to JSON", - "export-board-csv": "Export board to CSV", - "export-board-tsv": "Export board to TSV", - "export-board-excel": "Export board to Excel", - "user-can-not-export-excel": "User can not export Excel", - "export-board-html": "Export board to HTML", - "export-card": "Export card", - "export-card-pdf": "Export card to PDF", - "user-can-not-export-card-to-pdf": "User can not export card to PDF", - "exportBoardPopup-title": "Export board", - "exportCardPopup-title": "Export card", - "sort": "Sort", - "sorted": "Sorted", - "remove-sort": "Remove sort", - "sort-desc": "Click to Sort List", - "list-sort-by": "Sort the List By:", - "list-label-modifiedAt": "Last Access Time", - "list-label-title": "Name of the List", - "list-label-sort": "Your Manual Order", - "list-label-short-modifiedAt": "(L)", - "list-label-short-title": "(N)", - "list-label-short-sort": "(M)", - "filter": "Filter", - "filter-cards": "Filter Cards or Lists", - "filter-dates-label": "Filter by date", - "filter-no-due-date": "No due date", - "filter-overdue": "Overdue", - "filter-due-today": "Due today", - "filter-due-this-week": "Due this week", - "filter-due-next-week": "Due next week", - "filter-due-tomorrow": "Due tomorrow", - "list-filter-label": "Filter List by Title", - "filter-clear": "Clear filter", - "filter-labels-label": "Filter by label", - "filter-no-label": "No label", - "filter-member-label": "Filter by member", - "filter-no-member": "No member", - "filter-assignee-label": "Filter by assignee", - "filter-no-assignee": "No assignee", - "filter-custom-fields-label": "Filter by Custom Fields", - "filter-no-custom-fields": "No Custom Fields", - "filter-show-archive": "Show archived lists", - "filter-hide-empty": "Hide empty lists", - "filter-on": "Filter is on", - "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", - "filter-to-selection": "Filter to selection", - "other-filters-label": "Other Filters", - "advanced-filter-label": "Advanced Filter", - "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", - "fullname": "Full Name", - "header-logo-title": "Go back to your boards page.", - "show-activities": "Show Activities", - "headerBarCreateBoardPopup-title": "Create Board", - "home": "Home", - "import": "Import", - "impersonate-user": "Impersonate user", - "link": "Link", - "import-board": "import board", - "import-board-c": "Import board", - "import-board-title-trello": "Import board from Trello", - "import-board-title-wekan": "Import board from previous export", - "import-board-title-csv": "Import board from CSV/TSV", - "from-trello": "From Trello", - "from-wekan": "From previous export", - "from-csv": "From CSV/TSV", - "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", - "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", - "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", - "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", - "import-json-placeholder": "Paste your valid JSON data here", - "import-csv-placeholder": "Paste your valid CSV/TSV data here", - "import-map-members": "Map members", - "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", - "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", - "import-show-user-mapping": "Review members mapping", - "import-user-select": "Pick your existing user you want to use as this member", - "importMapMembersAddPopup-title": "Select member", - "info": "Version", - "initials": "Initials", - "invalid-date": "Invalid date", - "invalid-time": "Invalid time", - "invalid-user": "Invalid user", - "joined": "joined", - "just-invited": "You are just invited to this board", - "keyboard-shortcuts": "Keyboard shortcuts", - "label-create": "Create Label", - "label-default": "%s label (default)", - "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", - "labels": "Labels", - "language": "Language", - "last-admin-desc": "You can’t change roles because there must be at least one admin.", - "leave-board": "Leave Board", - "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", - "leaveBoardPopup-title": "Leave Board ?", - "link-card": "Link to this card", - "list-archive-cards": "Move all cards in this list to Archive", - "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", - "list-move-cards": "Move all cards in this list", - "list-select-cards": "Select all cards in this list", - "set-color-list": "Set Color", - "listActionPopup-title": "List Actions", - "settingsUserPopup-title": "User Settings", - "settingsTeamPopup-title": "Team Settings", - "settingsOrgPopup-title": "Organization Settings", - "swimlaneActionPopup-title": "Swimlane Actions", - "swimlaneAddPopup-title": "Add a Swimlane below", - "listImportCardPopup-title": "Import a Trello card", - "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", - "listMorePopup-title": "More", - "link-list": "Link to this list", - "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", - "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", - "lists": "Lists", - "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", - "log-out": "Log Out", - "log-in": "Log In", - "loginPopup-title": "Log In", - "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", - "members": "Members", - "menu": "Menu", - "move-selection": "Move selection", - "moveCardPopup-title": "Move Card", - "moveCardToBottom-title": "Move to Bottom", - "moveCardToTop-title": "Move to Top", - "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", - "multi-selection": "Multi-Selection", - "multi-selection-label": "Set label for selection", - "multi-selection-member": "Set member for selection", - "multi-selection-on": "Multi-Selection is on", - "muted": "Muted", - "muted-info": "You will never be notified of any changes in this board", - "my-boards": "My Boards", - "name": "Name", - "no-archived-cards": "No cards in Archive.", - "no-archived-lists": "No lists in Archive.", - "no-archived-swimlanes": "No swimlanes in Archive.", - "no-results": "No results", - "normal": "Normal", - "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", - "not-accepted-yet": "Invitation not accepted yet", - "notify-participate": "Receive updates to any cards you participate as creator or member", - "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", - "optional": "optional", - "or": "or", - "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", - "page-not-found": "Page not found.", - "password": "Password", - "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", - "participating": "Participating", - "preview": "Preview", - "previewAttachedImagePopup-title": "Preview", - "previewClipboardImagePopup-title": "Preview", - "private": "Private", - "private-desc": "This board is private. Only people added to the board can view and edit it.", - "profile": "Profile", - "public": "Public", - "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", - "quick-access-description": "Star a board to add a shortcut in this bar.", - "remove-cover": "Remove cover image from minicard", - "remove-from-board": "Remove from Board", - "remove-label": "Remove Label", - "listDeletePopup-title": "Delete List ?", - "remove-member": "Remove Member", - "remove-member-from-card": "Remove from Card", - "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", - "removeMemberPopup-title": "Remove Member?", - "rename": "Rename", - "rename-board": "Rename Board", - "restore": "Restore", - "rescue-card-description": "Show rescue dialogue before closing for unsaved card descriptions", - "rescue-card-description-dialogue": "Overwrite current card description with your changes?", - "save": "Save", - "search": "Search", - "rules": "Rules", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Write text you search and press Enter", - "select-color": "Select Color", - "select-board": "Select Board", - "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", - "setWipLimitPopup-title": "Set WIP Limit", - "shortcut-add-self": "Add yourself to current card", - "shortcut-assign-self": "Assign yourself to current card", - "shortcut-autocomplete-emoji": "Autocomplete emoji", - "shortcut-autocomplete-members": "Autocomplete members", - "shortcut-clear-filters": "Clear all filters", - "shortcut-close-dialog": "Close Dialog", - "shortcut-filter-my-cards": "Filter my cards", - "shortcut-filter-my-assigned-cards": "Filter my assigned cards", - "shortcut-show-shortcuts": "Bring up this shortcuts list", - "shortcut-toggle-filterbar": "Toggle Filter Sidebar", - "shortcut-toggle-searchbar": "Toggle Search Sidebar", - "shortcut-toggle-sidebar": "Toggle Board Sidebar", - "show-cards-minimum-count": "Show cards count if list contains more than", - "sidebar-open": "Open Sidebar", - "sidebar-close": "Close Sidebar", - "signupPopup-title": "Create an Account", - "star-board-title": "Click to star this board. It will show up at top of your boards list.", - "starred-boards": "Starred Boards", - "starred-boards-description": "Starred boards show up at the top of your boards list.", - "subscribe": "Subscribe", - "team": "Team", - "this-board": "this board", - "this-card": "this card", - "spent-time-hours": "Spent time (hours)", - "overtime-hours": "Overtime (hours)", - "overtime": "Overtime", - "has-overtime-cards": "Has overtime cards", - "has-spenttime-cards": "Has spent time cards", - "time": "Time", - "title": "Title", - "toggle-assignees": "Toggle assignees 1-9 for card (By order of addition to board).", - "toggle-labels": "Toggle labels 1-9 for card. Multi-Selection adds labels 1-9", - "remove-labels-multiselect": "Multi-Selection removes labels 1-9", - "tracking": "Tracking", - "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", - "type": "Type", - "unassign-member": "Unassign member", - "unsaved-description": "You have an unsaved description.", - "unwatch": "Unwatch", - "upload": "Upload", - "upload-avatar": "Upload an avatar", - "uploaded-avatar": "Uploaded an avatar", - "uploading-files": "Uploading files", - "upload-failed": "Upload failed", - "upload-completed": "Upload completed", - "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", - "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", - "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", - "custom-login-logo-image-url": "Custom Login Logo Image URL", - "custom-login-logo-link-url": "Custom Login Logo Link URL", - "custom-help-link-url": "Custom Help Link URL", - "text-below-custom-login-logo": "Text below Custom Login Logo", - "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", - "username": "Username", - "import-usernames": "Import Usernames", - "view-it": "View it", - "warn-list-archived": "warning: this card is in an list at Archive", - "watch": "Watch", - "watching": "Watching", - "watching-info": "You will be notified of any change in this board", - "welcome-board": "Welcome Board", - "welcome-swimlane": "Milestone 1", - "welcome-list1": "Basics", - "welcome-list2": "Advanced", - "card-templates-swimlane": "Card Templates", - "list-templates-swimlane": "List Templates", - "board-templates-swimlane": "Board Templates", - "what-to-do": "What do you want to do?", - "wipLimitErrorPopup-title": "Invalid WIP Limit", - "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", - "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", - "admin-panel": "Admin Panel", - "settings": "Settings", - "people": "People", - "registration": "Registration", - "disable-self-registration": "Disable Self-Registration", - "disable-forgot-password": "Disable Forgot Password", - "invite": "Invite", - "invite-people": "Invite People", - "to-boards": "To board(s)", - "email-addresses": "Email Addresses", - "smtp-host-description": "The address of the SMTP server that handles your emails.", - "smtp-port-description": "The port your SMTP server uses for outgoing emails.", - "smtp-tls-description": "Enable TLS support for SMTP server", - "smtp-host": "SMTP Host", - "smtp-port": "SMTP Port", - "smtp-username": "Username", - "smtp-password": "Password", - "smtp-tls": "TLS support", - "send-from": "From", - "send-smtp-test": "Send a test email to yourself", - "invitation-code": "Invitation Code", - "email-invite-register-subject": "__inviter__ sent you an invitation", - "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", - "email-smtp-test-subject": "SMTP Test Email", - "email-smtp-test-text": "You have successfully sent an email", - "error-invitation-code-not-exist": "Invitation code doesn't exist", - "error-notAuthorized": "You are not authorized to view this page.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional for Authentication)", - "outgoing-webhooks": "Outgoing Webhooks", - "bidirectional-webhooks": "Two-Way Webhooks", - "outgoingWebhooksPopup-title": "Outgoing Webhooks", - "boardCardTitlePopup-title": "Card Title Filter", - "disable-webhook": "Disable This Webhook", - "global-webhook": "Global Webhooks", - "new-outgoing-webhook": "New Outgoing Webhook", - "no-name": "(Unknown)", - "Node_version": "Node version", - "Meteor_version": "Meteor version", - "MongoDB_version": "MongoDB version", - "MongoDB_storage_engine": "MongoDB storage engine", - "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", - "OS_Arch": "OS Arch", - "OS_Cpus": "OS CPU Count", - "OS_Freemem": "OS Free Memory", - "OS_Loadavg": "OS Load Average", - "OS_Platform": "OS Platform", - "OS_Release": "OS Release", - "OS_Totalmem": "OS Total Memory", - "OS_Type": "OS Type", - "OS_Uptime": "OS Uptime", - "days": "days", - "hours": "hours", - "minutes": "minutes", - "seconds": "seconds", - "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Add field to new cards", - "always-field-on-card": "Add field to all cards", - "showLabel-field-on-card": "Show field label on minicard", - "showSum-field-on-list": "Show sum of fields at top of list", - "yes": "Yes", - "no": "No", - "accounts": "Accounts", - "accounts-allowEmailChange": "Allow Email Change", - "accounts-allowUserNameChange": "Allow Username Change", - "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", - "createdAt": "Created at", - "modifiedAt": "Modified at", - "verified": "Verified", - "active": "Active", - "card-received": "Received", - "card-received-on": "Received on", - "card-end": "End", - "card-end-on": "Ends on", - "editCardReceivedDatePopup-title": "Change received date", - "editCardEndDatePopup-title": "Change end date", - "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", - "assigned-by": "Assigned By", - "requested-by": "Requested By", - "card-sorting-by-number": "Card sorting by number", - "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", - "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", - "boardDeletePopup-title": "Delete Board?", - "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", - "delete-duplicate-lists": "Delete Duplicate Lists", - "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", - "default-subtasks-board": "Subtasks for __board__ board", - "default": "Default", - "defaultdefault": "Default", - "queue": "Queue", - "subtask-settings": "Subtasks Settings", - "card-settings": "Card Settings", - "minicard-settings": "Minicard Settings", - "boardSubtaskSettingsPopup-title": "Subtasks Settings", - "boardCardSettingsPopup-title": "Card Settings", - "boardMinicardSettingsPopup-title": "Minicard Settings", - "deposit-subtasks-board": "Deposit subtasks to this board:", - "deposit-subtasks-list": "Landing list for subtasks deposited here:", - "show-parent-in-minicard": "Show parent in minicard:", - "description-on-minicard": "Description on minicard", - "cover-attachment-on-minicard": "Cover image on minicard", - "badge-attachment-on-minicard": "Count of attachments on minicard", - "card-sorting-by-number-on-minicard": "Card sorting by number on minicard", - "prefix-with-full-path": "Prefix with full path", - "prefix-with-parent": "Prefix with parent", - "subtext-with-full-path": "Subtext with full path", - "subtext-with-parent": "Subtext with parent", - "change-card-parent": "Change card's parent", - "parent-card": "Parent card", - "source-board": "Source board", - "no-parent": "Don't show parent", - "activity-added-label": "added label '%s' to %s", - "activity-removed-label": "removed label '%s' from %s", - "activity-delete-attach": "deleted an attachment from %s", - "activity-added-label-card": "added label '%s'", - "activity-removed-label-card": "removed label '%s'", - "activity-delete-attach-card": "deleted an attachment", - "activity-set-customfield": "set custom field '%s' to '%s' in %s", - "activity-unset-customfield": "unset custom field '%s' in %s", - "r-rule": "Rule", - "r-add-trigger": "Add trigger", - "r-add-action": "Add action", - "r-board-rules": "Board rules", - "r-add-rule": "Add rule", - "r-view-rule": "View rule", - "r-delete-rule": "Delete rule", - "r-new-rule-name": "New rule title", - "r-no-rules": "No rules", - "r-trigger": "Trigger", - "r-action": "Action", - "r-when-a-card": "When a card", - "r-is": "is", - "r-is-moved": "is moved", - "r-added-to": "Added to", - "r-removed-from": "Removed from", - "r-the-board": "the board", - "r-list": "list", - "set-filter": "Set Filter", - "r-moved-to": "Moved to", - "r-moved-from": "Moved from", - "r-archived": "Moved to Archive", - "r-unarchived": "Restored from Archive", - "r-a-card": "a card", - "r-when-a-label-is": "When a label is", - "r-when-the-label": "When the label", - "r-list-name": "list name", - "r-when-a-member": "When a member is", - "r-when-the-member": "When the member", - "r-name": "name", - "r-when-a-attach": "When an attachment", - "r-when-a-checklist": "When a checklist is", - "r-when-the-checklist": "When the checklist", - "r-completed": "Completed", - "r-made-incomplete": "Made incomplete", - "r-when-a-item": "When a checklist item is", - "r-when-the-item": "When the checklist item", - "r-checked": "Checked", - "r-unchecked": "Unchecked", - "r-move-card-to": "Move card to", - "r-top-of": "Top of", - "r-bottom-of": "Bottom of", - "r-its-list": "its list", - "r-archive": "Move to Archive", - "r-unarchive": "Restore from Archive", - "r-card": "card", - "r-add": "Add", - "r-remove": "Remove", - "r-label": "label", - "r-member": "member", - "r-remove-all": "Remove all members from the card", - "r-set-color": "Set color to", - "r-checklist": "checklist", - "r-check-all": "Check all", - "r-uncheck-all": "Uncheck all", - "r-items-check": "items of checklist", - "r-check": "Check", - "r-uncheck": "Uncheck", - "r-item": "item", - "r-of-checklist": "of checklist", - "r-send-email": "Send an email", - "r-to": "to", - "r-of": "of", - "r-subject": "subject", - "r-rule-details": "Rule details", - "r-d-move-to-top-gen": "Move card to top of its list", - "r-d-move-to-top-spec": "Move card to top of list", - "r-d-move-to-bottom-gen": "Move card to bottom of its list", - "r-d-move-to-bottom-spec": "Move card to bottom of list", - "r-d-send-email": "Send email", - "r-d-send-email-to": "to", - "r-d-send-email-subject": "subject", - "r-d-send-email-message": "message", - "r-d-archive": "Move card to Archive", - "r-d-unarchive": "Restore card from Archive", - "r-d-add-label": "Add label", - "r-d-remove-label": "Remove label", - "r-create-card": "Create new card", - "r-in-list": "in list", - "r-in-swimlane": "in swimlane", - "r-d-add-member": "Add member", - "r-d-remove-member": "Remove member", - "r-d-remove-all-member": "Remove all member", - "r-d-check-all": "Check all items of a list", - "r-d-uncheck-all": "Uncheck all items of a list", - "r-d-check-one": "Check item", - "r-d-uncheck-one": "Uncheck item", - "r-d-check-of-list": "of checklist", - "r-d-add-checklist": "Add checklist", - "r-d-remove-checklist": "Remove checklist", - "r-by": "by", - "r-add-checklist": "Add checklist", - "r-with-items": "with items", - "r-items-list": "item1,item2,item3", - "r-add-swimlane": "Add swimlane", - "r-swimlane-name": "swimlane name", - "r-board-note": "Note: leave a field empty to match every possible value. ", - "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", - "r-when-a-card-is-moved": "When a card is moved to another list", - "r-set": "Set", - "r-update": "Update", - "r-datefield": "date field", - "r-df-start-at": "start", - "r-df-due-at": "due", - "r-df-end-at": "end", - "r-df-received-at": "received", - "r-to-current-datetime": "to current date/time", - "r-remove-value-from": "Remove value from", - "r-link-card": "Link card to", - "ldap": "LDAP", - "oauth2": "OAuth2", - "cas": "CAS", - "authentication-method": "Authentication method", - "authentication-type": "Authentication type", - "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", - "layout": "Layout", - "hide-logo": "Hide Logo", - "hide-card-counter-list": "Hide card counter list on All Boards", - "hide-board-member-list": "Hide board member list on All Boards", - "add-custom-html-after-body-start": "Add Custom HTML after <body> start", - "add-custom-html-before-body-end": "Add Custom HTML before </body> end", - "error-undefined": "Something went wrong", - "error-ldap-login": "An error occurred while trying to login", - "display-authentication-method": "Display Authentication Method", - "oidc-button-text": "Customize the OIDC button text", - "default-authentication-method": "Default Authentication Method", - "duplicate-board": "Duplicate Board", - "duplicate-board-confirm": "Are you sure you want to duplicate this board?", - "org-number": "The number of organizations is: ", - "team-number": "The number of teams is: ", - "people-number": "The number of people is: ", - "swimlaneDeletePopup-title": "Delete Swimlane ?", - "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", - "restore-all": "Restore all", - "delete-all": "Delete all", - "loading": "Loading, please wait.", - "previous_as": "last time was", - "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", - "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", - "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", - "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", - "a-dueAt": "modified due time to be", - "a-endAt": "modified ending time to be", - "a-startAt": "modified starting time to be", - "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", - "almostdue": "current due time %s is approaching", - "pastdue": "current due time %s is past", - "duenow": "current due time %s is today", - "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", - "act-withDue": "__list__/__card__ due reminders [__board__]", - "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", - "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", - "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", - "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", - "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", - "accounts-allowUserDelete": "Allow users to self delete their account", - "hide-minicard-label-text": "Hide minicard label text", - "show-desktop-drag-handles": "Show desktop drag handles", - "assignee": "Assignee", - "cardAssigneesPopup-title": "Assignee", - "addmore-detail": "Add a more detailed description", - "show-on-card": "Show on Card", - "show-on-minicard": "Show on Minicard", - "new": "New", - "editOrgPopup-title": "Edit Organization", - "newOrgPopup-title": "New Organization", - "editTeamPopup-title": "Edit Team", - "newTeamPopup-title": "New Team", - "editUserPopup-title": "Edit User", - "newUserPopup-title": "New User", - "notifications": "Notifications", - "help": "Help", - "view-all": "View All", - "filter-by-unread": "Filter by Unread", - "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", - "remove-all-read": "Remove all read", - "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename", - "start-day-of-week": "Set day of the week start", - "monday": "Monday", - "tuesday": "Tuesday", - "wednesday": "Wednesday", - "thursday": "Thursday", - "friday": "Friday", - "saturday": "Saturday", - "sunday": "Sunday", - "status": "Status", - "swimlane": "Swimlane", - "owner": "Owner", - "last-modified-at": "Last modified at", - "last-activity": "Last activity", - "voting": "Voting", - "archived": "Archived", - "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", - "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", - "hide-checked-items": "Hide checked items", - "hide-finished-checklist": "Hide finished checklist", - "task": "Task", - "create-task": "Create Task", - "ok": "OK", - "organizations": "Organizations", - "teams": "Teams", - "displayName": "Display Name", - "shortName": "Short Name", - "autoAddUsersWithDomainName": "Automatically add users with the domain name", - "website": "Website", - "person": "Person", - "my-cards": "My Cards", - "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", - "list": "List", - "board": "Board", - "context-separator": "/", - "myCardsViewChange-title": "My Cards View", - "myCardsViewChangePopup-title": "My Cards View", - "myCardsViewChange-choice-boards": "Boards", - "myCardsViewChange-choice-table": "Table", - "myCardsSortChange-title": "My Cards Sort", - "myCardsSortChangePopup-title": "My Cards Sort", - "myCardsSortChange-choice-board": "By Board", - "myCardsSortChange-choice-dueat": "By Due Date", - "dueCards-title": "Due Cards", - "dueCardsViewChange-title": "Due Cards View", - "dueCardsViewChangePopup-title": "Due Cards View", - "dueCardsViewChange-choice-me": "Me", - "dueCardsViewChange-choice-all": "All Users", - "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", - "dueCards-noResults-title": "No Due Cards Found", - "dueCards-noResults-description": "You don't have any cards with due dates at the moment.", - "broken-cards": "Broken Cards", - "board-title-not-found": "Board '%s' not found.", - "swimlane-title-not-found": "Swimlane '%s' not found.", - "list-title-not-found": "List '%s' not found.", - "label-not-found": "Label '%s' not found.", - "label-color-not-found": "Label color %s not found.", - "user-username-not-found": "Username '%s' not found.", - "comment-not-found": "Card with comment containing text '%s' not found.", - "org-name-not-found": "Organization '%s' not found.", - "team-name-not-found": "Team '%s' not found.", - "globalSearch-title": "Search All Boards", - "no-cards-found": "No Cards Found", - "one-card-found": "One Card Found", - "n-cards-found": "%s Cards Found", - "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", - "operator-board": "board", - "operator-board-abbrev": "b", - "operator-swimlane": "swimlane", - "operator-swimlane-abbrev": "s", - "operator-list": "list", - "operator-list-abbrev": "l", - "operator-label": "label", - "operator-label-abbrev": "#", - "operator-user": "user", - "operator-user-abbrev": "@", - "operator-member": "member", - "operator-member-abbrev": "m", - "operator-assignee": "assignee", - "operator-assignee-abbrev": "a", - "operator-creator": "creator", - "operator-status": "status", - "operator-due": "due", - "operator-created": "created", - "operator-modified": "modified", - "operator-sort": "sort", - "operator-comment": "comment", - "operator-has": "has", - "operator-limit": "limit", - "operator-debug": "debug", - "operator-org": "org", - "operator-team": "team", - "predicate-archived": "archived", - "predicate-open": "open", - "predicate-ended": "ended", - "predicate-all": "all", - "predicate-overdue": "overdue", - "predicate-week": "week", - "predicate-month": "month", - "predicate-quarter": "quarter", - "predicate-year": "year", - "predicate-due": "due", - "predicate-modified": "modified", - "predicate-created": "created", - "predicate-attachment": "attachment", - "predicate-description": "description", - "predicate-checklist": "checklist", - "predicate-start": "start", - "predicate-end": "end", - "predicate-assignee": "assignee", - "predicate-member": "member", - "predicate-public": "public", - "predicate-private": "private", - "predicate-selector": "selector", - "predicate-projection": "projection", - "operator-unknown-error": "%s is not an operator", - "operator-number-expected": "operator __operator__ expected a number, got '__value__'", - "operator-sort-invalid": "sort of '%s' is invalid", - "operator-status-invalid": "'%s' is not a valid status", - "operator-has-invalid": "%s is not a valid existence check", - "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", - "operator-debug-invalid": "%s is not a valid debug predicate", - "next-page": "Next Page", - "previous-page": "Previous Page", - "heading-notes": "Notes", - "globalSearch-instructions-heading": "Search Instructions", - "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", - "globalSearch-instructions-operators": "Available operators:", - "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", - "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", - "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", - "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", - "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", - "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", - "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", - "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", - "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", - "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", - "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", - "globalSearch-instructions-operator-org": "`__operator_org__:<display name|short name>` - cards belonging to a board assigned to organization *<name>*", - "globalSearch-instructions-operator-team": "`__operator_team__:<display name|short name>` - cards belonging to a board assigned to team *<name>*", - "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", - "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", - "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", - "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", - "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", - "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", - "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", - "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", - "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", - "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", - "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", - "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", - "globalSearch-instructions-notes-1": "Multiple operators may be specified.", - "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", - "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", - "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", - "globalSearch-instructions-notes-4": "Text searches are case insensitive.", - "globalSearch-instructions-notes-5": "By default archived cards are not searched.", - "link-to-search": "Link to this search", - "excel-font": "Arial", - "number": "Number", - "label-colors": "Label Colors", - "label-names": "Label Names", - "archived-at": "archived at", - "sort-cards": "Sort Cards", - "sort-is-on": "Sort is on", - "cardsSortPopup-title": "Sort Cards", - "due-date": "Due Date", - "server-error": "Server Error", - "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", - "title-alphabetically": "Title (Alphabetically)", - "created-at-newest-first": "Created At (Newest First)", - "created-at-oldest-first": "Created At (Oldest First)", - "links-heading": "Links", - "hide-activities-of-all-boards": "Don't show the board activities on all boards", - "now-activities-of-all-boards-are-hidden": "Now all activities of all boards are hidden", - "move-swimlane": "Move Swimlane", - "moveSwimlanePopup-title": "Move Swimlane", - "custom-field-stringtemplate": "String Template", - "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", - "custom-field-stringtemplate-separator": "Separator (use or   for a space)", - "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", - "creator": "Creator", - "creator-on-minicard": "Creator on minicard", - "filesReportTitle": "Files Report", - "reports": "Reports", - "rulesReportTitle": "Rules Report", - "boardsReportTitle": "Boards Report", - "cardsReportTitle": "Cards Report", - "copy-swimlane": "Copy Swimlane", - "copySwimlanePopup-title": "Copy Swimlane", - "display-card-creator": "Display Card Creator", - "wait-spinner": "Wait Spinner", - "Bounce": "Bounce Wait Spinner", - "Cube": "Cube Wait Spinner", - "Cube-Grid": "Cube-Grid Wait Spinner", - "Dot": "Dot Wait Spinner", - "Double-Bounce": "Double Bounce Wait Spinner", - "Rotateplane": "Rotateplane Wait Spinner", - "Scaleout": "Scaleout Wait Spinner", - "Wave": "Wave Wait Spinner", - "maximize-card": "Maximize Card", - "minimize-card": "Minimize Card", - "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", - "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it", - "subject": "Subject", - "details": "Details", - "carbon-copy": "Carbon Copy (Cc:)", - "ticket": "Ticket", - "tickets": "Tickets", - "ticket-number": "Ticket Number", - "open": "Open", - "pending": "Pending", - "closed": "Closed", - "resolved": "Resolved", - "cancelled": "Cancelled", - "history": "History", - "request": "Request", - "requests": "Requests", - "help-request": "Help Request", - "editCardSortOrderPopup-title": "Change Sorting", - "cardDetailsPopup-title": "Card Details", - "add-teams": "Add teams", - "add-teams-label": "Added teams are displayed below:", - "remove-team-from-table": "Are you sure you want to remove this team from the board ?", - "confirm-btn": "Confirm", - "remove-btn": "Remove", - "filter-card-title-label": "Filter by card title", - "invite-people-success": "Invitation to register sent with success", - "invite-people-error": "Error while sending invitation to register", - "can-invite-if-same-mailDomainName": "Email domain name", - "to-create-teams-contact-admin": "To create teams, please contact the administrator.", - "Node_heap_total_heap_size": "Node heap: total heap size", - "Node_heap_total_heap_size_executable": "Node heap: total heap size executable", - "Node_heap_total_physical_size": "Node heap: total physical size", - "Node_heap_total_available_size": "Node heap: total available size", - "Node_heap_used_heap_size": "Node heap: used heap size", - "Node_heap_heap_size_limit": "Node heap: heap size limit", - "Node_heap_malloced_memory": "Node heap: malloced memory", - "Node_heap_peak_malloced_memory": "Node heap: peak malloced memory", - "Node_heap_does_zap_garbage": "Node heap: does zap garbage", - "Node_heap_number_of_native_contexts": "Node heap: number of native contexts", - "Node_heap_number_of_detached_contexts": "Node heap: number of detached contexts", - "Node_memory_usage_rss": "Node memory usage: resident set size", - "Node_memory_usage_heap_total": "Node memory usage: total size of the allocated heap", - "Node_memory_usage_heap_used": "Node memory usage: actual memory used", - "Node_memory_usage_external": "Node memory usage: external", - "add-organizations": "Add organizations", - "add-organizations-label": "Added organizations are displayed below:", - "remove-organization-from-board": "Are you sure you want to remove this organization from this board ?", - "to-create-organizations-contact-admin": "To create organizations, please contact administrator.", - "custom-legal-notice-link-url": "Custom legal notice page URL", - "acceptance_of_our_legalNotice": "By continuing, you accept our", - "legalNotice": "legal notice", - "copied": "Copied!", - "checklistActionsPopup-title": "Checklist Actions", - "moveChecklist": "Move Checklist", - "moveChecklistPopup-title": "Move Checklist", - "newlineBecomesNewChecklistItem": "Each line of text becomes one of the checklist items", - "newLineNewItem": "One line of text = one checklist item", - "newlineBecomesNewChecklistItemOriginOrder": "Each line of text becomes one of the checklist items, original order", - "originOrder": "original order", - "copyChecklist": "Copy Checklist", - "copyChecklistPopup-title": "Copy Checklist", - "card-show-lists": "Card Show Lists", - "subtaskActionsPopup-title": "Subtask Actions", - "attachmentActionsPopup-title": "Attachment Actions", - "attachment-move-storage-fs": "Move attachment to filesystem", - "attachment-move-storage-gridfs": "Move attachment to GridFS", - "attachment-move-storage-s3": "Move attachment to S3", - "attachment-move": "Move Attachment", - "move-all-attachments-to-fs": "Move all attachments to filesystem", - "move-all-attachments-to-gridfs": "Move all attachments to GridFS", - "move-all-attachments-to-s3": "Move all attachments to S3", - "move-all-attachments-of-board-to-fs": "Move all attachments of board to filesystem", - "move-all-attachments-of-board-to-gridfs": "Move all attachments of board to GridFS", - "move-all-attachments-of-board-to-s3": "Move all attachments of board to S3", - "path": "Path", - "version-name": "Version-Name", - "size": "Size", - "storage": "Storage", - "action": "Action", - "board-title": "Board Title", - "attachmentRenamePopup-title": "Rename", - "uploading": "Uploading", - "remaining_time": "Remaining time", - "speed": "Speed", - "progress": "Progress", - "password-again": "Password (again)", - "if-you-already-have-an-account": "If you already have an account", - "register": "Register", - "forgot-password": "Forgot password", - "minicardDetailsActionsPopup-title": "Card Details", - "Mongo_sessions_count": "Mongo sessions count", - "change-visibility": "Change Visibility", - "max-upload-filesize": "Max upload filesize in bytes:", - "allowed-upload-filetypes": "Allowed upload filetypes:", - "max-avatar-filesize": "Max avatar filesize in bytes:", - "allowed-avatar-filetypes": "Allowed avatar filetypes:", - "invalid-file": "If filename is invalid, upload or rename is cancelled.", - "preview-pdf-not-supported": "Your device does not support previewing PDF. Try downloading instead.", - "drag-board": "Drag board", - "translation-number": "The number of custom translation strings is:", - "delete-translation-confirm-popup": "Are you sure you want to delete this custom translation string? There is no undo.", - "newTranslationPopup-title": "New custom translation string", - "editTranslationPopup-title": "Edit custom translation string", - "settingsTranslationPopup-title": "Delete this custom translation string?", - "translation": "Translation", - "text": "Text", - "translation-text": "Translation text", - "show-subtasks-field": "Show subtasks field", - "show-week-of-year": "Show week of year (ISO 8601)", - "convert-to-markdown": "Convert to markdown", - "import-board-zip": "Add .zip file that has board JSON files, and board name subdirectories with attachments", - "collapse": "Collapse", - "uncollapse": "Uncollapse", - "hideCheckedChecklistItems": "Hide checked checklist items", - "hideAllChecklistItems": "Hide all checklist items", - "support": "Support", - "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", - "accessibility": "Accessibility", - "accessibility-page-enabled": "Accessibility page enabled", - "accessibility-info-not-added-yet": "Accessibility info has not been added yet", - "accessibility-title": "Accessibility title", - "accessibility-content": "Accessibility content", - "accounts-lockout-settings": "Brute Force Protection Settings", - "accounts-lockout-info": "These settings control how login attempts are protected against brute force attacks.", - "accounts-lockout-known-users": "Settings for known users (correct username, wrong password)", - "accounts-lockout-unknown-users": "Settings for unknown users (non-existent username)", - "accounts-lockout-failures-before": "Failures before lockout", - "accounts-lockout-period": "Lockout period (seconds)", - "accounts-lockout-failure-window": "Failure window (seconds)", - "accounts-lockout-settings-updated": "Brute force protection settings have been updated", - "accounts-lockout-locked-users": "Locked Users", - "accounts-lockout-locked-users-info": "Users currently locked out due to too many failed login attempts", - "accounts-lockout-no-locked-users": "There are currently no locked users", - "accounts-lockout-failed-attempts": "Failed Attempts", - "accounts-lockout-remaining-time": "Remaining Time", - "accounts-lockout-user-unlocked": "User has been unlocked successfully", - "accounts-lockout-confirm-unlock": "Are you sure you want to unlock this user?", - "accounts-lockout-confirm-unlock-all": "Are you sure you want to unlock all locked users?", - "accounts-lockout-show-locked-users": "Show locked users only", - "accounts-lockout-user-locked": "User is locked", - "accounts-lockout-click-to-unlock": "Click to unlock this user", - "accounts-lockout-status": "Status", - "admin-people-filter-show": "Show:", - "admin-people-filter-all": "All Users", - "admin-people-filter-locked": "Locked Users Only", - "admin-people-filter-active": "Active", - "admin-people-filter-inactive": "Not Active", - "admin-people-active-status": "Active Status", - "admin-people-user-active": "User is active - click to deactivate", - "admin-people-user-inactive": "User is inactive - click to activate", - "accounts-lockout-all-users-unlocked": "All locked users have been unlocked", - "accounts-lockout-unlock-all": "Unlock All", - "active-cron-jobs": "Active Scheduled Jobs", - "add-cron-job": "Add Scheduled Job", - "add-cron-job-placeholder": "Add Scheduled Job functionality coming soon", - "attachment-storage-configuration": "Attachment Storage Configuration", - "attachments-path": "Attachments Path", - "attachments-path-description": "Path where attachment files are stored", - "avatars-path": "Avatars Path", - "avatars-path-description": "Path where avatar files are stored", - "board-archive-failed": "Failed to schedule board archive", - "board-archive-scheduled": "Board archive scheduled successfully", - "board-backup-failed": "Failed to schedule board backup", - "board-backup-scheduled": "Board backup scheduled successfully", - "board-cleanup-failed": "Failed to schedule board cleanup", - "board-cleanup-scheduled": "Board cleanup scheduled successfully", - "board-operations": "Board Operations", - "cron-jobs": "Scheduled Jobs", - "cron-migrations": "Scheduled Migrations", - "cron-job-delete-confirm": "Are you sure you want to delete this scheduled job?", - "cron-job-delete-failed": "Failed to delete scheduled job", - "cron-job-deleted": "Scheduled job deleted successfully", - "cron-job-pause-failed": "Failed to pause scheduled job", - "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", - "filesystem-path-description": "Base path for file storage", - "gridfs-enabled": "GridFS Enabled", - "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", - "migration-pause-failed": "Failed to pause migrations", - "migration-paused": "Migrations paused successfully", - "migration-progress": "Migration Progress", - "migration-start-failed": "Failed to start migrations", - "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", - "migration-status": "Migration Status", - "migration-stop-confirm": "Are you sure you want to stop all migrations?", - "migration-stop-failed": "Failed to stop migrations", - "migration-stopped": "Migrations stopped successfully", - "mongodb-gridfs-storage": "MongoDB GridFS Storage", - "pause-all-migrations": "Pause All Migrations", - "s3-access-key": "S3 Access Key", - "s3-access-key-description": "AWS S3 access key for authentication", - "s3-access-key-placeholder": "Enter S3 access key", - "s3-bucket": "S3 Bucket", - "s3-bucket-description": "S3 bucket name for storing files", - "s3-connection-failed": "S3 connection failed", - "s3-connection-success": "S3 connection successful", - "s3-enabled": "S3 Enabled", - "s3-enabled-description": "Use AWS S3 or MinIO for file storage", - "s3-endpoint": "S3 Endpoint", - "s3-endpoint-description": "S3 endpoint URL (e.g., s3.amazonaws.com or minio.example.com)", - "s3-minio-storage": "S3/MinIO Storage", - "s3-port": "S3 Port", - "s3-port-description": "S3 endpoint port number", - "s3-region": "S3 Region", - "s3-region-description": "AWS S3 region (e.g., us-east-1)", - "s3-secret-key": "S3 Secret Key", - "s3-secret-key-description": "AWS S3 secret key for authentication", - "s3-secret-key-placeholder": "Enter S3 secret key", - "s3-secret-key-required": "S3 secret key is required", - "s3-settings-save-failed": "Failed to save S3 settings", - "s3-settings-saved": "S3 settings saved successfully", - "s3-ssl-enabled": "S3 SSL Enabled", - "s3-ssl-enabled-description": "Use SSL/TLS for S3 connections", - "save-s3-settings": "Save S3 Settings", - "schedule-board-archive": "Schedule Board Archive", - "schedule-board-backup": "Schedule Board Backup", - "schedule-board-cleanup": "Schedule Board Cleanup", - "scheduled-board-operations": "Scheduled Board Operations", - "start-all-migrations": "Start All Migrations", - "stop-all-migrations": "Stop All Migrations", - "test-s3-connection": "Test S3 Connection", - "writable-path": "Writable Path", - "writable-path-description": "Base directory path for file storage", - "add-job": "Add Job", - "attachment-migration": "Attachment Migration", - "attachment-monitoring": "Attachment Monitoring", - "attachment-settings": "Attachment Settings", - "attachment-storage-settings": "Storage Settings", - "automatic-migration": "Automatic Migration", - "back-to-settings": "Back to Settings", - "board-id": "Board ID", - "board-migration": "Board Migration", - "board-migrations": "Board Migrations", - "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", - "cleanup": "Cleanup", - "cleanup-old-jobs": "Cleanup Old Jobs", - "completed": "Completed", - "conversion-info-text": "This conversion is performed once per board and improves performance. You can continue using the board normally.", - "converting-board": "Converting Board", - "converting-board-description": "Converting board structure for improved functionality. This may take a few moments.", - "cpu-cores": "CPU Cores", - "cpu-usage": "CPU Usage", - "current-action": "Current Action", - "database-migration": "Database Migration", - "database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.", - "database-migrations": "Database Migrations", - "days-old": "Days Old", - "duration": "Duration", - "errors": "Errors", - "estimated-time-remaining": "Estimated time remaining", - "every-1-day": "Every 1 day", - "every-1-hour": "Every 1 hour", - "every-1-minute": "Every 1 minute", - "every-10-minutes": "Every 10 minutes", - "every-30-minutes": "Every 30 minutes", - "every-5-minutes": "Every 5 minutes", - "every-6-hours": "Every 6 hours", - "export-monitoring": "Export Monitoring", - "filesystem-attachments": "Filesystem Attachments", - "filesystem-size": "Filesystem Size", - "filesystem-storage": "Filesystem Storage", - "force-board-scan": "Force Board Scan", - "gridfs-attachments": "GridFS Attachments", - "gridfs-size": "GridFS Size", - "gridfs-storage": "GridFS", - "hide-list-on-minicard": "Hide List on Minicard", - "idle-migration": "Idle Migration", - "job-description": "Job Description", - "job-details": "Job Details", - "job-name": "Job Name", - "job-queue": "Job Queue", - "last-run": "Last Run", - "max-concurrent": "Max Concurrent", - "memory-usage": "Memory Usage", - "migrate-all-to-filesystem": "Migrate All to Filesystem", - "migrate-all-to-gridfs": "Migrate All to GridFS", - "migrate-all-to-s3": "Migrate All to S3", - "migrated-attachments": "Migrated Attachments", - "migration-batch-size": "Batch Size", - "migration-batch-size-description": "Number of attachments to process in each batch (1-100)", - "migration-cpu-threshold": "CPU Threshold (%)", - "migration-cpu-threshold-description": "Pause migration when CPU usage exceeds this percentage (10-90)", - "migration-delay-ms": "Delay (ms)", - "migration-delay-ms-description": "Delay between batches in milliseconds (100-10000)", - "migration-detector": "Migration Detector", - "migration-info-text": "Database migrations are performed once and improve system performance. The process continues in the background even if you close your browser.", - "migration-log": "Migration Log", - "migration-markers": "Migration Markers", - "migration-resume-failed": "Failed to resume migration", - "migration-resumed": "Migration resumed", - "migration-steps": "Migration Steps", - "migration-warning-text": "Please do not close your browser during migration. The process will continue in the background but may take longer to complete.", - "monitoring-export-failed": "Failed to export monitoring data", - "monitoring-refresh-failed": "Failed to refresh monitoring data", - "next": "Next", - "next-run": "Next Run", - "of": "of", - "operation-type": "Operation Type", - "overall-progress": "Overall Progress", - "page": "Page", - "pause-migration": "Pause Migration", - "previous": "Previous", - "refresh": "Refresh", - "refresh-monitoring": "Refresh Monitoring", - "remaining-attachments": "Remaining Attachments", - "resume-migration": "Resume Migration", - "run-once": "Run once", - "s3-attachments": "S3 Attachments", - "s3-size": "S3 Size", - "s3-storage": "S3", - "scanning-status": "Scanning Status", - "schedule": "Schedule", - "search-boards-or-operations": "Search boards or operations...", - "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", - "showing": "Showing", - "start-test-operation": "Start Test Operation", - "start-time": "Start Time", - "step-progress": "Step Progress", - "stop-migration": "Stop Migration", - "storage-distribution": "Storage Distribution", - "system-resources": "System Resources", - "total-attachments": "Total Attachments", - "total-operations": "Total Operations", - "total-size": "Total Size", - "unmigrated-boards": "Unmigrated Boards", - "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" -} diff --git a/imports/i18n/data/ko-KR.i18n.json b/imports/i18n/data/ko-KR.i18n.json index e125a70ae..284d8e20b 100644 --- a/imports/i18n/data/ko-KR.i18n.json +++ b/imports/i18n/data/ko-KR.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "너비 설정", "set-list-width": "너비 설정", "set-list-width-value": "최소 & 최대 너비 설정 (픽셀)", @@ -122,10 +109,10 @@ "add-after-list": "목록 뒤에 추가", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "배경 이미지 URL", "add-background-image": "배경 이미지 추가", "remove-background-image": "배경 이미지 제거", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "모든 확인목록 항목 숨기기", "support": "지원", "supportPopup-title": "지원", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "접근성 페이지 활성화됨", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ko.i18n.json b/imports/i18n/data/ko.i18n.json index 3af2bdee3..1796716ba 100644 --- a/imports/i18n/data/ko.i18n.json +++ b/imports/i18n/data/ko.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "삭제된 댓글", "activity-receivedDate": "수신 날짜를 %s의 %s로 수정함", "activity-startDate": "시작 날짜가 %s의 %s로 수정됨", - "allboards.starred": "Starred", - "allboards.templates": "템플릿", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "마감 날짜가 %s의 %s로 수정됨", "activity-endDate": "종료 날짜가 %s의 %s로 수정됨", "add-attachment": "첨부파일 추가", @@ -98,7 +86,6 @@ "add-card": "카드 추가", "add-card-to-top-of-list": "리스트 맨앞에 카드를 추가함", "add-card-to-bottom-of-list": "리스트 맨뒤에 카드를 추가함", - "addListPopup-title": "리스트 추가", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "목록 뒤에 추가", "add-members": "멤버 추가", "added": "추가됨", - "addMemberPopup-title": "멤버 추가", + "addMemberPopup-title": "멤버", "memberPopup-title": "멤버 설정", "admin": "관리자", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "카드를 보거나 수정하고, 멤버를 삭제하고, 보드에 대한 설정을 수정할 수 있습니다.", "admin-announcement": "공지사항", "admin-announcement-active": "시스템에 공지사항을 표시합니다", "admin-announcement-title": "관리자 공지사항 메시지", @@ -167,16 +154,12 @@ "board-background-image-url": "배경 이미지 URL", "add-background-image": "배경 이미지 추가", "remove-background-image": "배경 이미지 제거", - "show-at-all-boards-page": "모든 게시판 페이지에서 보이기", - "board-info-on-my-boards": "모든 게시판 설정", - "boardInfoOnMyBoardsPopup-title": "모든 게시판 설정", + "show-at-all-boards-page" : "모든 게시판 페이지에서 보이기", + "board-info-on-my-boards" : "모든 게시판 설정", + "boardInfoOnMyBoardsPopup-title" : "모든 게시판 설정", "boardInfoOnMyBoards-title": "모든 게시판 설정", "show-card-counter-per-list": "목록 당 표시할 카드 수", "show-board_members-avatar": "게시판 회원 아바타 표시", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s개의 별", "board-not-found": "보드를 찾을 수 없습니다", "board-private-info": "이 보드는 <strong>비공개</strong>입니다.", @@ -286,8 +269,6 @@ "change-permissions": "권한 변경", "change-settings": "설정 변경", "changeAvatarPopup-title": "아바타 변경", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "언어 변경", "changePasswordPopup-title": "암호 변경", "changePermissionsPopup-title": "권한 변경", @@ -335,16 +316,10 @@ "comment-placeholder": "댓글 입력", "comment-only": "댓글만 입력 가능", "comment-only-desc": "카드에 댓글만 달수 있습니다.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "댓글을 삭제하시겠습니까?", "deleteCommentPopup-title": "댓글을 삭제하시겠습니까?", "no-comments": "댓글 없음", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "댓글과 활동내역을 볼 수 없습니다.", "worker": "노동자", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "내 컴퓨터", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "체크리스트를 삭제하시겠습니까?", "subtaskDeletePopup-title": "하위태스크를 삭제합니까?", "checklistDeletePopup-title": "체크리스트를 삭제합니까?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "클립보드에 카드의 링크가 복사되었습니다.", "copy-text-to-clipboard": "텍스트를 클립보드에 복사합니다", "linkCardPopup-title": "카드 연결", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "생성", "createBoardPopup-title": "보드 생성", - "createTemplateContainerPopup-title": "템플릿 컨테이너 추가", "chooseBoardSourcePopup-title": "보드 가져오기", "createLabelPopup-title": "라벨 생성", "createCustomField": "필드 생성", @@ -385,7 +358,7 @@ "date": "날짜", "date-format": "날짜 형식", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "쇠퇴", "default-avatar": "기본 아바타", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "목록들", "swimlanes": "Swimlanes", - "calendar": "달력", - "gantt": "간트", "log-out": "로그아웃", "log-in": "로그인", "loginPopup-title": "로그인", "memberMenuPopup-title": "멤버 설정", - "grey-icons": "Grey Icons", "members": "멤버", "menu": "메뉴", "move-selection": "선택 항목 이동", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "최하단으로 이동", "moveCardToTop-title": "최상단으로 이동", "moveSelectionPopup-title": "선택 항목 이동", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "다중 선택", "multi-selection-label": "선택에 대한 라벨 설정", "multi-selection-member": "선택에 대한 구성원 설정", @@ -587,8 +555,6 @@ "no-results": "결과 값 없음", "normal": "표준", "normal-desc": "카드를 보거나 수정할 수 있습니다. 설정값은 변경할 수 없습니다.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "초대장이 수락되지 않았습니다.", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "감시중인 보드, 목록 또는 카드에 대한 변경사항 알림 받음", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "유저 이름 변경 허용", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "에 변경됨", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "색 설정", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "색상 선택", "setSwimlaneColorPopup-title": "색상 선택", "setListColorPopup-title": "색상 선택", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "모든 목록, 카드, 레이블 및 활동이 삭제되고 보드 내용을 복구할 수 없습니다. 실행 취소는 불가능합니다.", "boardDeletePopup-title": "보드 삭제?", "delete-board": "보드 삭제", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "중복 목록 삭제", "delete-duplicate-lists-confirm": "확실합니까? 이렇게 하면 이름이 같고 카드가 없는 모든 중복 목록이 삭제됩니다.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "인증 방법", "authentication-type": "인증 유형", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "레이아웃", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "이 조직을 정말 지우시겠습니까? 실행 취소가 불가능합니다.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "카드", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "목록", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "시간", - "cron-error-message": "Error Message", - "cron-error-details": "상세", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "완료", - "idle": "유휴", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "시작일", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "완료", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "상세", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "완료", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "가중치", - "cron": "스케줄러", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "확인", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "유휴", + "complete": "완료", + "cron": "스케줄러" } diff --git a/imports/i18n/data/lt.i18n.json b/imports/i18n/data/lt.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/lt.i18n.json +++ b/imports/i18n/data/lt.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/lv.i18n.json b/imports/i18n/data/lv.i18n.json index 0ef7d2202..d051fb8a5 100644 --- a/imports/i18n/data/lv.i18n.json +++ b/imports/i18n/data/lv.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "dzēsa komentāru %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Sagataves", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Pievienot failu", @@ -98,7 +86,6 @@ "add-card": "Pievienot kartiņu", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Pievienot sarakstu", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Pievienot dalībniekus", "added": "Pievienots", - "addMemberPopup-title": "Pievienot dalībniekus", + "addMemberPopup-title": "Dalībnieki", "memberPopup-title": "Dalībnieka iestatījumi", "admin": "Administrators", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Var skatīt un labot kartiņas, noņemt dalībniekus un mainīt dēļa iestatījumus", "admin-announcement": "Paziņojums", "admin-announcement-active": "Aktīvizēts paziņojums visā sistēmā", "admin-announcement-title": "Paziņojums no administratora", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s zvaigznes", "board-not-found": "Dēlis nav atrasts", "board-private-info": "Šis dēlis būs <strong>privāts</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Mainīt atļaujas", "change-settings": "Iestatījumi", "changeAvatarPopup-title": "Mainīt attēlu", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Mainīt valodu", "changePasswordPopup-title": "Mainīt paroli", "changePermissionsPopup-title": "Mainīt atļaujas", @@ -335,16 +316,10 @@ "comment-placeholder": "Rakstīt komentāru", "comment-only": "Tikai komentēt", "comment-only-desc": "Var komentēt tikai kartiņā.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "Nav komentāru", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Nevar redzēt komentārus un darbības.", "worker": "Darbonis", "worker-desc": "Var tikai pārvietot kartiņas, pievienot sevi kartiņai un komentēt.", "computer": "Dators", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Kopet kartiņas saiti starpliktuvē", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Kartiņas saite", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Pirmās kartiņas virsraksts\", \"description\":\"Pirmās kartiņas apraksts}, {\"title\":\"Otrās kartiņas virsraksts\",\"description\":\"Otrās kartiņas apraksts\"},{\"title\":\"Pēdējās kartiņas virsraksts\",\"description\":\"Pēdējās kartiņas apraksts\"} ]", "create": "Izveidot", "createBoardPopup-title": "Izveidot dēli", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Importēt dēli", "createLabelPopup-title": "Izveidot birku", "createCustomField": "Izveidot lauku", @@ -385,7 +358,7 @@ "date": "Datums", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Noraidīt", "default-avatar": "Noklusētais attēls", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Lai saglabātu darbības, sarakstu var pārvieto uz arhīvu.", "lists": "Saraksti", "swimlanes": "Joslas", - "calendar": "Kalendārs", - "gantt": "Gantt", "log-out": "Izrakstīties", "log-in": "Pierakstīties", "loginPopup-title": "Pierakstīties", "memberMenuPopup-title": "Dalībnieka iestatījumi", - "grey-icons": "Grey Icons", "members": "Dalībnieki", "menu": "Izvēlne", "move-selection": "Pārvietot atzīmēto", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Pārvietot uz apakšu", "moveCardToTop-title": "Pārvietot uz augšu", "moveSelectionPopup-title": "Pārvietot atzīmēto", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Atzīmēt", "multi-selection-label": "Pielikt birku atzīmētajam", "multi-selection-member": "Pielikt dalībnieku atzīmētajam", @@ -587,8 +555,6 @@ "no-results": "Nav rezultātu", "normal": "Normāls", "normal-desc": "Var skatīties un labot kartiņas. Nevar mainīt iestatījumus.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Ielūgums nav vēl apstiprināts", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Saņemt ziņojumus no dēļiem, sarakstiem vai kartiņām kurām sekojat", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Ļaut e-pasta maiņu", "accounts-allowUserNameChange": "Ļaut lietotājvārda maiņu", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Izveidots", "modifiedAt": "Modified at", "verified": "Apstiprināts", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Mainīt saņemšanas datumu", "editCardEndDatePopup-title": "Mainīt beigu datumu", "setCardColorPopup-title": "Mainīt krāsu", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Izvēlieties krāsu", "setSwimlaneColorPopup-title": "Izvēlieties krāsu", "setListColorPopup-title": "Izvēlieties krāsu", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Visi saraksti, kartiņas, birkas un darbības tiks dzēstas un dēli nevarēs atgūt. Darbība nav atsaucama.", "boardDeletePopup-title": "Dzēst dēli?", "delete-board": "Dzēst dēli", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Apakšuzdevumi priekš __board__ dēļa", @@ -942,13 +905,6 @@ "authentication-method": "Autentifikācijas metode", "authentication-type": "Autentifikācijas veids", "custom-product-name": "Personalizēts produkta nosaukums", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Izkārtojums", "hide-logo": "Paslēt logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "laboja beigu laiku", "a-startAt": "laboja sākuma laiku", "a-receivedAt": "laboja saņemšanas laiku lai tas būtu", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "tuvojas nodošanas laiks %s", "pastdue": "pārsniegts nodošanas laiks %s", "duenow": "nodošanas laiks ir šodien %s", @@ -989,7 +943,7 @@ "act-almostdue": "atgādināja, ka nodošanas laiks (__timeValue__) priekš __card__ tuvojas", "act-pastdue": "atgādināja, ka nodošanas laiks (__timeValue__) priekš __card__ ir pārsniegts", "act-duenow": "atgādināja, ka nodošanas laiks (__timeValue__) priekš __card__ ir tagad", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Jūs pieminēja [__board__] __list__/__card__", "delete-user-confirm-popup": "Tiešām dzēst kontu? Darbību nevar atsaukt.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "Skatīt visu", "filter-by-unread": "Rādīt nelasīto", "mark-all-as-read": "Atzīmēt visu kā lasītu", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Noņemt visu lasīto", "allow-rename": "Ļaut pārsaukt", "allowRenamePopup-title": "Ļaut pārsaukt", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Kartiņa", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Laiks", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Sākt", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Statuss", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Pabeigts", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/mk.i18n.json b/imports/i18n/data/mk.i18n.json index 6a9592498..491feeb6e 100644 --- a/imports/i18n/data/mk.i18n.json +++ b/imports/i18n/data/mk.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Додај прилог", @@ -98,7 +86,6 @@ "add-card": "Додади Картичка", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Додади листа", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Додави членови", "added": "Додадено", - "addMemberPopup-title": "Додави членови", + "addMemberPopup-title": "Членови", "memberPopup-title": "Настройки на профила", "admin": "Администратор", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Съобщение", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s звезди", "board-not-found": "Таблото не е најдено", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Промени права", "change-settings": "Промени параметри", "changeAvatarPopup-title": "Промени аватар", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Промени јазик", "changePasswordPopup-title": "Промени лозинка", "changePermissionsPopup-title": "Промени права", @@ -335,16 +316,10 @@ "comment-placeholder": "Напиши коментар", "comment-only": "Само коментари", "comment-only-desc": "Може да коментира само в карти.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "Нема коментари", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Компјутер", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Копирай връзката на картата в клипборда", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Поврзи картичка", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Креирај", "createBoardPopup-title": "Креирај Табло", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Импортирай Табло", "createLabelPopup-title": "Креирај Табло", "createCustomField": "Креирај Поле", @@ -385,7 +358,7 @@ "date": "Дата", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Откажи", "default-avatar": "Основен аватар", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Можете да преместите списъка во Архива, за да го премахнете от Таблото и така да запазите активността в него.", "lists": "Листи", "swimlanes": "Коридори", - "calendar": "Календар", - "gantt": "Gantt", "log-out": "Изход", "log-in": "Вход", "loginPopup-title": "Вход", "memberMenuPopup-title": "Настройки на профила", - "grey-icons": "Grey Icons", "members": "Членови", "menu": "Меню", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Премести в края", "moveCardToTop-title": "Премести в началото", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Множествен избор", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Получавате информация за всички табла, списъци и карти, които наблюдавате", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Разреши промяна на имейла", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Създаден на", "modifiedAt": "Modified at", "verified": "Потвърден", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Промени датата на получаване", "editCardEndDatePopup-title": "Промени датата на завършване", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Изтриване на Таблото?", "delete-board": "Изтрий таблото", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Подзадачи за табло __board__", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Карта", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Време", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Започнува", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/mn.i18n.json b/imports/i18n/data/mn.i18n.json index 3ec6dd7a5..de4627964 100644 --- a/imports/i18n/data/mn.i18n.json +++ b/imports/i18n/data/mn.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Хавсралт нэмэх", @@ -98,7 +86,6 @@ "add-card": "Карт нэмэх", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Жагсаалт нэмэх", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Гишүүд нэмэх", "added": "Нэмсэн", - "addMemberPopup-title": "Гишүүд нэмэх", + "addMemberPopup-title": "Гишүүд", "memberPopup-title": "Гишүүний тохиргоо", "admin": "Админ", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Тохиргоо өөрчлөх", "changeAvatarPopup-title": "Аватар өөрчлөх", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Хэл солих", "changePasswordPopup-title": "Нууц үг солих", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Үүсгэх", "createBoardPopup-title": "Самбар үүсгэх", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Шошго үүсгэх", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Гарах", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Гишүүний тохиргоо", - "grey-icons": "Grey Icons", "members": "Гишүүд", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ms-MY.i18n.json b/imports/i18n/data/ms-MY.i18n.json index cacb8ef63..e1ea29b8a 100644 --- a/imports/i18n/data/ms-MY.i18n.json +++ b/imports/i18n/data/ms-MY.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Bertanda", - "allboards.templates": "Templates", - "allboards.remaining": "Baki", - "allboards.workspaces": "Ruang kerja", - "allboards.add-workspace": "Tambah Ruang kerja", - "allboards.add-workspace-prompt": "Nama ruang kerja", - "allboards.add-subworkspace": "Tambah sub-ruang kerja", - "allboards.add-subworkspace-prompt": "Nama sub-ruang kerja", - "allboards.edit-workspace": "Ubah ruang kerja", - "allboards.edit-workspace-name": "Nama ruang kerja", - "allboards.edit-workspace-icon": "Ikon ruang-kerja (markdown)", - "multi-selection-active": "Klik kotak semak untuk pilih papan", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Tambah Senarai", "setListWidthPopup-title": "Tetapkan Lebar", "set-list-width": "Tetapkan Lebar", "set-list-width-value": "Tetapkan lebar minimum dan maksimum (piksel)", @@ -122,10 +109,10 @@ "add-after-list": "Tambah selepas senarai", "add-members": "Tambah Ahli", "added": "Ditambah", - "addMemberPopup-title": "Tambah Ahli", + "addMemberPopup-title": "Ahli-ahli", "memberPopup-title": "Tetapan Ahli", "admin": "Pentadbir", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Pengumuman", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Pengumuman dari Pentadbir", @@ -167,16 +154,12 @@ "board-background-image-url": "URL imej latar", "add-background-image": "Tambah imej latar", "remove-background-image": "Hapus imej latar", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Hanya peruntukkan komen", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Baca Sahaja", - "read-only-desc": "Kad hanya untuk paparan. Ubahsuai disekat.", - "read-assigned-only": "Berikan 'Baca' Sahaja", - "read-assigned-only-desc": "Hanya papar kad yang ditugaskan sahaja. Ubahsuai disekat.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Hapus item senarai semak?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Format Tarikh", "date-format-yyyy-mm-dd": "TTTT-BB-HH", - "date-format-dd-mm-yyyy": "HH-BB-TTTT", + "date-format-dd-mm-yyyy": "HH-BB-TTTT", "date-format-mm-dd-yyyy": "BB-HH-TTTT", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Ikon Kelabu", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Salin pilihan", - "selection-color": "Warna Pilihan", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Hanya Berikan Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "Kad Saya", "card": "Kad", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Senarai", "board": "Papan", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Maklumat", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Maklumat", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Sahkan", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ms.i18n.json b/imports/i18n/data/ms.i18n.json index cc467e30f..ad26e0f97 100644 --- a/imports/i18n/data/ms.i18n.json +++ b/imports/i18n/data/ms.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Semua Templat", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Ahli", "memberPopup-title": "Tetapan Ahli", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Pengumuman", "admin-announcement-active": "Pengumuman Aktif Seluruh Sistem", "admin-announcement-title": "Pengumuman daripada Pengurusan", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Papar laman semua Papan", - "board-info-on-my-boards": "Semua tetapan Papa", - "boardInfoOnMyBoardsPopup-title": "Semua tetapan Papa", + "show-at-all-boards-page" : "Papar laman semua Papan", + "board-info-on-my-boards" : "Semua tetapan Papa", + "boardInfoOnMyBoardsPopup-title" : "Semua tetapan Papa", "boardInfoOnMyBoards-title": "Semua tetapan Papa", "show-card-counter-per-list": "Papar jumlah Kad setiap Senarai", "show-board_members-avatar": "Papar avatar ahli Papan", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%sbintang", "board-not-found": "Papan tidak dijumpai", "board-private-info": "Papan ini akan menjadi <strong>peribadi </strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Tukar Kebenaran", "change-settings": "Tukar Tetapan", "changeAvatarPopup-title": "Tukar Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Tukar Bahasa", "changePasswordPopup-title": "Tukar Kata Laluan", "changePermissionsPopup-title": "Tukar Kebenaran", @@ -335,16 +316,10 @@ "comment-placeholder": "Tulis Komen", "comment-only": "hanya komen", "comment-only-desc": "hanya boleh komen pada kad sahaja", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Anda pasti untuk hapus komen ini?", "deleteCommentPopup-title": "Hapus komen?", "no-comments": "tiada komen", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Tidak dapat melihat komen dan aktiviti", "worker": "Pekerja", "worker-desc": "hanya boleh pindah kad, tugaskan diri sendiri pada kad dan komen", "computer": "Komputer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Anda pasti untuk hapus senarai semak?", "subtaskDeletePopup-title": "Hapus subtugas?", "checklistDeletePopup-title": "Hapus Senarai semak?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Salin capaian kad ke papan klip", "copy-text-to-clipboard": "Salin teks ke papan klip", "linkCardPopup-title": "Paut Kad", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Cipta", "createBoardPopup-title": "Cipta Papan", - "createTemplateContainerPopup-title": "Tambah Bekas Templat", "chooseBoardSourcePopup-title": "Import Papan", "createLabelPopup-title": "Cipta Label", "createCustomField": "Cipta ruangan", @@ -385,7 +358,7 @@ "date": "Tarikh", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Ditolak", "default-avatar": "Avatar Lalai", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Anda boleh pindahkan senarai ke Arkib untuk membuangnya daripada papan bagi memelihara aktivitinya.", "lists": "Senarai", "swimlanes": "Aliran Renang", - "calendar": "Kalendar", - "gantt": "Gantt", "log-out": "Log Keluar", "log-in": "Log Masuk", "loginPopup-title": "Log Masuk", "memberMenuPopup-title": "Tetapan Ahli", - "grey-icons": "Grey Icons", "members": "Ahli", "menu": "Menu", "move-selection": "Pindah Pilihan", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Pindah ke bawah", "moveCardToTop-title": "Pidah ke atas", "moveSelectionPopup-title": "Pindah Pilihan", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Pelbagai pilihan", "multi-selection-label": "Set label untuk pilihan", "multi-selection-member": "Set ahli untuk pilihan", @@ -587,8 +555,6 @@ "no-results": "Tiada keputusan", "normal": "Normal", "normal-desc": "Boleh Papar dan ubah suai kad. Tidak boleh ubah tetapan.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Jemputan belum dibalas.", "notify-participate": "Terima maklumbalas daripada mana-mana kad yang anda sertai sebagai pencipta atau ahli.", "notify-watch": "Terima maklumbalas daripada mana-mana papan, senarai, atau kad yang anda perhatikan.", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Benarkan Penukaran e-mel", "accounts-allowUserNameChange": "Benarkan Perubahan Nama Pengguna", "tableVisibilityMode-allowPrivateOnly": "Kebolehlihatan Papan: Hanya Benarkan Papan Peribadi", - "tableVisibilityMode": "Kebolehlihatan Papan", + "tableVisibilityMode" : "Kebolehlihatan Papan", "createdAt": "Dicipta pada", "modifiedAt": "Diubah pada", "verified": "Disahkan", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Ubah masa terima", "editCardEndDatePopup-title": "Ubah masa tamat", "setCardColorPopup-title": "Set Warna", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Pilih warna", "setSwimlaneColorPopup-title": "Pilih warna", "setListColorPopup-title": "Pilih warna", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Semua senarai, kad, label dan aktiviti akan dihapus dan anda tidak akan dapat pulihkan semula kandungan papan. Tiada undur semula", "boardDeletePopup-title": "Hapus Papan?", "delete-board": "Hapus papan", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "subtugas untuk __board__ papan", @@ -942,13 +905,6 @@ "authentication-method": "Kaedah pengesahan", "authentication-type": "Jenis pengesahan", "custom-product-name": "Nama Produk Khas", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Susunatur", "hide-logo": "Sembunyikan logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "tarikh tamat telah diubah menjadi", "a-startAt": "tarikh mula telah diubah menjadi", "a-receivedAt": "tarikh terima telah diubah menjadi", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Kad", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Senarai", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Masa", - "cron-error-message": "Error Message", - "cron-error-details": "Perincian", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Mula", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Perincian", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Lengkap", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Sahkan", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/nb.i18n.json b/imports/i18n/data/nb.i18n.json index 823d643e5..f3fb0f0ed 100644 --- a/imports/i18n/data/nb.i18n.json +++ b/imports/i18n/data/nb.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "slettet kommentar %s", "activity-receivedDate": "redigert mottatt dato til %s av %s", "activity-startDate": "redigert startdato til %s av %s", - "allboards.starred": "Starred", - "allboards.templates": "Maler", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "redigert forfallsdato til %s av %s", "activity-endDate": "redigert sluttdato %s av %s", "add-attachment": "Legg til Vedlegg", @@ -98,7 +86,6 @@ "add-card": "Legg til Kort", "add-card-to-top-of-list": "Legg til Kort på Toppen av Listen", "add-card-to-bottom-of-list": "Legg til Kort på Bunnen av Listen", - "addListPopup-title": "Legg til Liste", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Legg til medlemmer", "added": "Lagt til", - "addMemberPopup-title": "Legg til medlemmer", + "addMemberPopup-title": "Medlemmer", "memberPopup-title": "Innstillinger Medlem", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Kan se og redigere kort, fjerne medlemmer og endre innstillingene for tavlen.", "admin-announcement": "Kunngjøring", "admin-announcement-active": "Aktiv systemkunngjøring", "admin-announcement-title": "Kunngjøring fra Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stjerner", "board-not-found": "Kunne ikke finne tavlen", "board-private-info": "Denne tavlen vil være <strong>privat</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Endre rettigheter", "change-settings": "Endre innstillinger", "changeAvatarPopup-title": "Endre avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Endre språk", "changePasswordPopup-title": "Endre passord", "changePermissionsPopup-title": "Endre tillatelser", @@ -335,16 +316,10 @@ "comment-placeholder": "Skriv kommentar", "comment-only": "Kun kommentar", "comment-only-desc": "Kun kommentar på kort.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Er du sikker på at du vil slette kommentaren?", "deleteCommentPopup-title": "Slette kommentaren?", "no-comments": "Ingen kommentarer", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Kan ikke se kommentarer eller aktiviteter.", "worker": "Arbeider", "worker-desc": "Kan bare flytte kort, tildele kort til seg selv og kommentere.", "computer": "Datamaskin", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Er du sikker på at du vil slette sjekklisten?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Slette sjekklisten?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Kopier lenke", "copy-text-to-clipboard": "Kopier tekst til utklippstavlen", "linkCardPopup-title": "Link Kort", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"Tittel\": \"Tittel første kort\", \"Beskrivelse\":\"Beskrivelse første kort\"}, {\"Tittel\":\"Tittel andre kort\",\"Beskrivelse\":\"Beskrivelse andre kort\"},{\"Tittel\":\"Tittel siste kort\",\"Beskrivelse\":\"Beskrivelse siste kort\"} ]", "create": "Opprett", "createBoardPopup-title": "Opprett Tavle", - "createTemplateContainerPopup-title": "Legg til Malgruppe", "chooseBoardSourcePopup-title": "Importer tavle", "createLabelPopup-title": "Opprett Etikett", "createCustomField": "Opprett Felt", @@ -385,7 +358,7 @@ "date": "Dato", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Avvis", "default-avatar": "Standard avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Du kan flytte en liste til Arkiv for å fjerne den fra Tavlen og bevare aktivitetene.", "lists": "Lister", "swimlanes": "Svømmebaner", - "calendar": "Kalender", - "gantt": "Gantt", "log-out": "Logg ut", "log-in": "Logg inn", "loginPopup-title": "Logg inn", "memberMenuPopup-title": "Innstillinger Medlem", - "grey-icons": "Grey Icons", "members": "Medlemmer", "menu": "Meny", "move-selection": "Flytt valgte", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Flytt til bunnen", "moveCardToTop-title": "Flytt til toppen", "moveSelectionPopup-title": "Flytt valgte", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Velg flere", "multi-selection-label": "Sett Etikett for valgte", "multi-selection-member": "Sett Medlem for valgte", @@ -587,8 +555,6 @@ "no-results": "Ingen resultat", "normal": "Normal", "normal-desc": "Kan se og redigere Kort. Kan ikke endre innstillinger.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitasjon foreløpig ikke akseptert", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Motta oppdatering av alle tavler, lister eller kort som du overvåker", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Tillat endring e-post", "accounts-allowUserNameChange": "Tillat endring brukernavn", "tableVisibilityMode-allowPrivateOnly": "Synlighet Tavle: Tillat kun private tavler", - "tableVisibilityMode": "Synlighet Tavler", + "tableVisibilityMode" : "Synlighet Tavler", "createdAt": "Opprettet på", "modifiedAt": "Endret kl.", "verified": "Verifisert", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Endre mottatt dato", "editCardEndDatePopup-title": "Endre sluttdato", "setCardColorPopup-title": "Sett farge", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Velg en farge", "setSwimlaneColorPopup-title": "Velg en farge", "setListColorPopup-title": "Velg en farge", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Alle lister, kort, etiketter og aktiviteter vil bli slettet og du vil ikke kunne gjenopprette innholdet på tavlen. Det er ikke mulig å angre.", "boardDeletePopup-title": "Slett Tavle?", "delete-board": "Slett Tavle", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Underoppgave for __board__ tavle", @@ -942,13 +905,6 @@ "authentication-method": "Autentiseringsmetode", "authentication-type": "Autentiseringstype", "custom-product-name": "Tilpasset Produktnavn", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Oppsett", "hide-logo": "Skjul Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "redigert slutt-tid til", "a-startAt": "redigert starttid til", "a-receivedAt": "redigert mottatt tid til", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "Nåværende forfallstid %s nærmer seg", "pastdue": "Nåværende forfallstid %s er passert", "duenow": "Nåværende forfallstid %s er i dag", @@ -989,7 +943,7 @@ "act-almostdue": "minnet om at nåværende forfallstid (__timeValue__) for __card__ nærmer seg", "act-pastdue": "minnet om at nåværende forfallstid (__timeValue__) for __card__ er passert", "act-duenow": "minnet om at nåværende forfallstid (__timeValue__) for __card__ er nå", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Du ble nevnt i [__board__] __list__/__card__", "delete-user-confirm-popup": "Er du sikker på at du vil slette denne kontoen?", "delete-team-confirm-popup": "Er du sikker på at du vil slette Teamet? Du kan ikke angre.", "delete-org-confirm-popup": "Er du sikker på at du vil slette denne Organisasjonen? Du kan ikke angre.", @@ -1013,7 +967,6 @@ "view-all": "Se Alle", "filter-by-unread": "Filtrer Uleste", "mark-all-as-read": "Merk alle som lest", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Fjerne alle leste", "allow-rename": "Tillat Omdøping", "allowRenamePopup-title": "Tillat Omdøping", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "Mine Kort", "card": "Kort", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Liste", "board": "Tavle", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Tid", - "cron-error-message": "Error Message", - "cron-error-details": "Detaljer", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Detaljer", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Gjennomført", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Bekreft", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/nl-NL.i18n.json b/imports/i18n/data/nl-NL.i18n.json index b3843d7af..e1e932403 100644 --- a/imports/i18n/data/nl-NL.i18n.json +++ b/imports/i18n/data/nl-NL.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "aantekening verwijderd %s", "activity-receivedDate": "ontvangst datum gewijzigd naar %s van %s", "activity-startDate": "start datum gewijzigd naar %s van %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "vervaldatum gewijzigd naar %s van %s", "activity-endDate": "einddatum gewijzigd naar %s van %s", "add-attachment": "Bijlage Toevoegen", @@ -98,7 +86,6 @@ "add-card": "Kaart Toevoegen", "add-card-to-top-of-list": "Kaart Boven Aan de Lijst Toevoegen", "add-card-to-bottom-of-list": "Kaart Onder Aan de Lijst Toevoegen", - "addListPopup-title": "Lijst Toevoegen", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Leden Toevoegen", "added": "Toegevoegd", - "addMemberPopup-title": "Leden Toevoegen", + "addMemberPopup-title": "Leden", "memberPopup-title": "Leden Instellingen", "admin": "Administrator", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Kan kaarten bekijken en wijzigen, leden verwijderen, en instellingen voor het bord aanpassen.", "admin-announcement": "Melding", "admin-announcement-active": "Systeem melding", "admin-announcement-title": "Melding van de beheerder", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Toon op Alle borden pagina", - "board-info-on-my-boards": "Alle borden instellingen", - "boardInfoOnMyBoardsPopup-title": "Alle borden instellingen", + "show-at-all-boards-page" : "Toon op Alle borden pagina", + "board-info-on-my-boards" : "Alle borden instellingen", + "boardInfoOnMyBoardsPopup-title" : "Alle borden instellingen", "boardInfoOnMyBoards-title": "Alle borden instellingen", "show-card-counter-per-list": "Toon aantal kaarten per lijst", "show-board_members-avatar": "Toon avatars van bord-leden", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s sterren", "board-not-found": "Bord is niet gevonden", "board-private-info": "Dit bord is nu <strong>privé</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Wijzig permissies", "change-settings": "Wijzig instellingen", "changeAvatarPopup-title": "Wijzig avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Wijzig taal", "changePasswordPopup-title": "Wijzig wachtwoord", "changePermissionsPopup-title": "Wijzig permissies", @@ -335,16 +316,10 @@ "comment-placeholder": "Schrijf aantekening", "comment-only": "Alleen aantekeningen maken", "comment-only-desc": "Kan alleen op kaarten aantekenen.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Weet je zeker dat je de aantekening wilt verwijderen?", "deleteCommentPopup-title": "Verwijder aantekening?", "no-comments": "Geen aantekeningen", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Zie geen aantekeningen of activiteiten.", "worker": "Medewerker", "worker-desc": "Kan alleen kaarten verplaatsen, zichzelf aan kaarten koppelen en aantekeningen maken.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Weet je zeker dat je de checklist wilt verwijderen?", "subtaskDeletePopup-title": "Subtaak Verwijderen?", "checklistDeletePopup-title": "Checklist Verwijderen?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Kopieer kaart link naar klembord", "copy-text-to-clipboard": "Kopieer tekst naar klembord", "linkCardPopup-title": "Koppel Kaart", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Titel eerste kaart\", \"description\":\"Omschrijving eerste kaart\"}, {\"title\":\"Titel tweede kaart\",\"description\":\"Omschrijving tweede kaart\"},{\"title\":\"Titel laatste kaart\",\"description\":\"Omschrijving laatste kaart\"} ]", "create": "Aanmaken", "createBoardPopup-title": "Bord aanmaken", - "createTemplateContainerPopup-title": "Template Container Toevoegen", "chooseBoardSourcePopup-title": "Importeer bord", "createLabelPopup-title": "Label aanmaken", "createCustomField": "Veld aanmaken", @@ -385,7 +358,7 @@ "date": "Datum", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Weigeren", "default-avatar": "Standaard avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Je kunt een lijst naar Archief verplaatsen om die van het bord te verwijderen waarbij de activiteiten behouden blijven.", "lists": "Lijsten", "swimlanes": "Swimlanes", - "calendar": "Kalender", - "gantt": "Gantt", "log-out": "Uitloggen", "log-in": "Inloggen", "loginPopup-title": "Inloggen", "memberMenuPopup-title": "Leden Instellingen", - "grey-icons": "Grey Icons", "members": "Leden", "menu": "Menu", "move-selection": "Verplaats selectie", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Verplaats naar beneden", "moveCardToTop-title": "Verplaats naar boven", "moveSelectionPopup-title": "Verplaats selectie", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-selectie", "multi-selection-label": "Stel label voor selectie in", "multi-selection-member": "Stel lid voor selectie in", @@ -587,8 +555,6 @@ "no-results": "Geen resultaten", "normal": "Normaal", "normal-desc": "Kan de kaarten zien en wijzigen. Kan de instellingen niet wijzigen.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Uitnodiging nog niet geaccepteerd", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Ontvang updates van elke bord, lijst of kaart die je bekijkt.", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Sta E-mailadres wijzigingen toe", "accounts-allowUserNameChange": "Sta Gebruikersnaam wijzigingen toe", "tableVisibilityMode-allowPrivateOnly": "Zichtbaarheid borden: Sta alleen privé borden toe", - "tableVisibilityMode": "Zichtbaarheid borden", + "tableVisibilityMode" : "Zichtbaarheid borden", "createdAt": "Aangemaakt op", "modifiedAt": "Gewijzigd op", "verified": "Geverifieerd", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Wijzig ontvangstdatum", "editCardEndDatePopup-title": "Wijzig einddatum", "setCardColorPopup-title": "Stel kleur in", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Kies een kleur", "setSwimlaneColorPopup-title": "Kies een kleur", "setListColorPopup-title": "Kies een kleur", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Alle lijsten, kaarten, labels en activiteiten zullen worden verwijderd en je kunt de bordinhoud niet terughalen. Er is geen herstelmogelijkheid.", "boardDeletePopup-title": "Bord verwijderen?", "delete-board": "Verwijder bord", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtaken voor __board__ bord", @@ -942,13 +905,6 @@ "authentication-method": "Authenticatiemethode", "authentication-type": "Authenticatietype", "custom-product-name": "Eigen Productnaam", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Lay-out", "hide-logo": "Verberg Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "einddatum gewijzigd naar", "a-startAt": "begindatum gewijzigd naar", "a-receivedAt": "ontvangstdatum gewijzigd naar", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "huidige vervaldatum %s nadert", "pastdue": "huidige vervaldatum %s is verlopen", "duenow": "huidige vervaldatum %s is vandaag", @@ -989,7 +943,7 @@ "act-almostdue": "wil je herinneren aan het naderen van de huidige vervaldatum (__timeValue__) van __card__", "act-pastdue": "wil je herinneren aan het verlopen van de huidige vervaldatum (__timeValue__) van __card__", "act-duenow": "wil je herinneren aan het vandaag verlopen van de huidige vervaldatum (__timeValue__) van __card__", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Je werd genoemd in [__board__] __list__/__card__", "delete-user-confirm-popup": "Weet je zeker dat je dit account wilt verwijderen? Er is geen herstelmogelijkheid.", "delete-team-confirm-popup": "Weet je zeker dat je dit team wilt verwijderen? Er is geen herstelmogelijkheid.", "delete-org-confirm-popup": "Weet je zeker dat je deze organisatie wilt verwijderen? Er is geen herstelmogelijkheid.", @@ -1013,7 +967,6 @@ "view-all": "Bekijk alles", "filter-by-unread": "Filter op Ongelezen", "mark-all-as-read": "Markeer alles als gelezen", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "verwijder alle gelezen", "allow-rename": "Sta Hernoemen toe", "allowRenamePopup-title": "Sta Hernoemen toe", @@ -1048,10 +1001,6 @@ "person": "Persoon", "my-cards": "Mijn kaarten", "card": "Kaart", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Lijst", "board": "Bord", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Tijd", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Begin", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Afgewerkt", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Bevestig", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/nl.i18n.json b/imports/i18n/data/nl.i18n.json index 90222640b..e854597fd 100644 --- a/imports/i18n/data/nl.i18n.json +++ b/imports/i18n/data/nl.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "aantekening verwijderd %s", "activity-receivedDate": "ontvangst datum gewijzigd naar %s van %s", "activity-startDate": "start datum gewijzigd naar %s van %s", - "allboards.starred": "Favorieten", - "allboards.templates": "Templates", - "allboards.remaining": "Resterend", - "allboards.workspaces": "Werkruimte", - "allboards.add-workspace": "Werkruimte toevoegen", - "allboards.add-workspace-prompt": "Werkruimtenaam", - "allboards.add-subworkspace": "Sub-werkruimte toevoegen", - "allboards.add-subworkspace-prompt": "Sub-werkruimtenaam", - "allboards.edit-workspace": "Wijzig werkruimte", - "allboards.edit-workspace-name": "Werkruimtenaam", - "allboards.edit-workspace-icon": "Werkruimteicoon (markdown)", - "multi-selection-active": "Vink de checkboxen om borden te selecteren", "activity-dueDate": "vervaldatum gewijzigd naar %s van %s", "activity-endDate": "einddatum gewijzigd naar %s van %s", "add-attachment": "Bijlage Toevoegen", @@ -98,7 +86,6 @@ "add-card": "Kaart Toevoegen", "add-card-to-top-of-list": "Kaart Boven Aan de Lijst Toevoegen", "add-card-to-bottom-of-list": "Kaart Onder Aan de Lijst Toevoegen", - "addListPopup-title": "Lijst Toevoegen", "setListWidthPopup-title": "Stel Breedte in", "set-list-width": "Stel Breedte in", "set-list-width-value": "Stel Min. & Max. Breedtes in (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Na Lijst Toevoegen", "add-members": "Leden Toevoegen", "added": "Toegevoegd", - "addMemberPopup-title": "Leden Toevoegen", + "addMemberPopup-title": "Leden", "memberPopup-title": "Leden Instellingen", "admin": "Administrator", - "admin-desc": "Kan kaarten bekijken en wijzigen, leden verwijderen en instellingen voor het bord aanpassen. Kan activiteiten bekijken.", + "admin-desc": "Kan kaarten bekijken en wijzigen, leden verwijderen, en instellingen voor het bord aanpassen.", "admin-announcement": "Melding", "admin-announcement-active": "Systeem melding", "admin-announcement-title": "Melding van de beheerder", @@ -167,16 +154,12 @@ "board-background-image-url": "URL Achtergrond Afbeelding", "add-background-image": "Voeg Achtergrondafbeelding Toe", "remove-background-image": "Verwijder Achtergrondafbeelding", - "show-at-all-boards-page": "Toon op Alle borden pagina", - "board-info-on-my-boards": "Alle borden instellingen", - "boardInfoOnMyBoardsPopup-title": "Alle borden instellingen", + "show-at-all-boards-page" : "Toon op Alle borden pagina", + "board-info-on-my-boards" : "Alle borden instellingen", + "boardInfoOnMyBoardsPopup-title" : "Alle borden instellingen", "boardInfoOnMyBoards-title": "Alle borden instellingen", "show-card-counter-per-list": "Toon aantal kaarten per lijst", "show-board_members-avatar": "Toon avatars van bord-leden", - "board_members": "Alle bord leden", - "card_members": "Alle leden van de huidige kaart op dit bord", - "board_assignees": "Alle toegewezen personen op alle kaarten op dit bord", - "card_assignees": "Alle toegewezen personen van de huidige kaart op dit bord", "board-nb-stars": "%s sterren", "board-not-found": "Bord is niet gevonden", "board-private-info": "Dit bord is nu <strong>privé</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Wijzig permissies", "change-settings": "Wijzig instellingen", "changeAvatarPopup-title": "Wijzig avatar", - "delete-avatar-confirm": "Weet je zeker dat je deze avatar wilt verwijderen?", - "deleteAvatarPopup-title": "Avatar Verwijderen?", "changeLanguagePopup-title": "Wijzig taal", "changePasswordPopup-title": "Wijzig wachtwoord", "changePermissionsPopup-title": "Wijzig permissies", @@ -335,16 +316,10 @@ "comment-placeholder": "Schrijf aantekening", "comment-only": "Alleen aantekeningen maken", "comment-only-desc": "Kan alleen op kaarten aantekenen.", - "comment-assigned-only": "Alleen Toegewezen Aantekening", - "comment-assigned-only-desc": "Alleen toegewezen kaarten zijn zichtbaar. Kan alleen aantekeningen toevoegen.", "comment-delete": "Weet je zeker dat je de aantekening wilt verwijderen?", "deleteCommentPopup-title": "Verwijder aantekening?", "no-comments": "Geen aantekeningen", - "no-comments-desc": "Zie geen aantekeningen.", - "read-only": "Alleen Lezen", - "read-only-desc": "Kan alleen kaarten bekijken. Kan niet wijzigen.", - "read-assigned-only": "Alleen Lezen Toegewezen", - "read-assigned-only-desc": "Alleen toegewezen kaarten zijn zichtbaar. Kan niet wijzigen.", + "no-comments-desc": "Zie geen aantekeningen of activiteiten.", "worker": "Medewerker", "worker-desc": "Kan alleen kaarten verplaatsen, zichzelf aan kaarten koppelen en aantekeningen maken.", "computer": "Bestandbeheer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Weet je zeker dat je de checklist wilt verwijderen?", "subtaskDeletePopup-title": "Subtaak Verwijderen?", "checklistDeletePopup-title": "Checklist Verwijderen?", - "checklistItemDeletePopup-title": "Checklist Item Verwijderen?", "copy-card-link-to-clipboard": "Kopieer kaart link naar klembord", "copy-text-to-clipboard": "Kopieer tekst naar klembord", "linkCardPopup-title": "Link Kaart", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Titel eerste kaart\", \"description\":\"Omschrijving eerste kaart\"}, {\"title\":\"Titel tweede kaart\",\"description\":\"Omschrijving tweede kaart\"},{\"title\":\"Titel laatste kaart\",\"description\":\"Omschrijving laatste kaart\"} ]", "create": "Aanmaken", "createBoardPopup-title": "Bord aanmaken", - "createTemplateContainerPopup-title": "Template Container Toevoegen", "chooseBoardSourcePopup-title": "Importeer bord", "createLabelPopup-title": "Label aanmaken", "createCustomField": "Veld aanmaken", @@ -385,7 +358,7 @@ "date": "Datum", "date-format": "Datumformaat", "date-format-yyyy-mm-dd": "JJJJ-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-JJJJ", + "date-format-dd-mm-yyyy": "DD-MM-JJJJ", "date-format-mm-dd-yyyy": "MM-DD-JJJJ", "decline": "Weigeren", "default-avatar": "Standaard avatar", @@ -542,7 +515,7 @@ "list-archive-cards-pop": "Dit zal alle kaarten uit deze lijst op dit bord verwijderen. Om de kaarten in het Archief te tonen en terug te halen, klik \"Menu\" > \"Archief\".", "list-move-cards": "Verplaats alle kaarten in deze lijst", "list-select-cards": "Selecteer alle kaarten in deze lijst", - "set-color-list": "Stel kleur in", + "set-color-list": "Wijzig kleur in", "listActionPopup-title": "Lijst acties", "settingsUserPopup-title": "Gebruiker Instellingen", "settingsTeamPopup-title": "Team Instellingen", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Je kunt een lijst naar Archief verplaatsen om die van het bord te verwijderen waarbij de activiteiten behouden blijven.", "lists": "Lijsten", "swimlanes": "Swimlanes", - "calendar": "Kalender", - "gantt": "Gantt", "log-out": "Uitloggen", "log-in": "Inloggen", "loginPopup-title": "Inloggen", "memberMenuPopup-title": "Leden Instellingen", - "grey-icons": "Grijze Iconen", "members": "Leden", "menu": "Menu", "move-selection": "Verplaats selectie", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Verplaats naar beneden", "moveCardToTop-title": "Verplaats naar boven", "moveSelectionPopup-title": "Verplaats selectie", - "copySelectionPopup-title": "Kopieer selectie", - "selection-color": "Geselecteerde Kleur", "multi-selection": "Multi-selectie", "multi-selection-label": "Stel label voor selectie in", "multi-selection-member": "Stel lid voor selectie in", @@ -586,9 +554,7 @@ "no-archived-swimlanes": "Geen swimlanes in Archief.", "no-results": "Geen resultaten", "normal": "Normaal", - "normal-desc": "Kan de kaarten bekijken en wijzigen. Kan de instellingen niet wijzigen.", - "normal-assigned-only": "Alleen Normaal Toegewezen", - "normal-assigned-only-desc": "Alleen toegewezen kaarten zijn zichtbaar. Wijzig als Normale gebruiker.", + "normal-desc": "Kan de kaarten zien en wijzigen. Kan de instellingen niet wijzigen.", "not-accepted-yet": "Uitnodiging nog niet geaccepteerd", "notify-participate": "Ontvang updates op kaarten waar je lid of maker van bent", "notify-watch": "Ontvang updates van elke bord, lijst of kaart die je bekijkt.", @@ -628,8 +594,8 @@ "search-example": "Schijf je zoektekst en toets Enter", "select-color": "Selecteer kleur", "select-board": "Selecteer Bord", - "set-wip-limit-value": "Stel een limiet voor het maximaal aantal taken in deze lijst in", - "setWipLimitPopup-title": "Stel WIP limiet in", + "set-wip-limit-value": "Zet een limiet voor het maximaal aantal taken in deze lijst", + "setWipLimitPopup-title": "Zet een WIP limiet", "shortcut-add-self": "Voeg jezelf toe aan huidige kaart", "shortcut-assign-self": "Voeg jezelf toe aan huidige kaart", "shortcut-autocomplete-emoji": "Emojis automatisch aanvullen", @@ -678,14 +644,14 @@ "custom-top-left-corner-logo-image-url": "URL Voor Maatwerk Logo Afbeelding In Linker Bovenhoek", "custom-top-left-corner-logo-link-url": "URL Voor Maatwerk Logo Link In Linker Bovenhoek", "custom-top-left-corner-logo-height": "Hoogte Van Maatwerk Logo In Linker Bovenhoek. Default: 27", - "custom-login-logo-image-url": "URL Voor Maatwerk Aanmeld Logo Afbeelding", - "custom-login-logo-link-url": "URL Voor Maatwerk Aanmeld Logo Link", + "custom-login-logo-image-url": "URL Voor Maatwerk Login Logo Afbeelding", + "custom-login-logo-link-url": "URL Voor Maatwerk Login Logo Link", "custom-help-link-url": "Maatwerk Help Link URL", - "text-below-custom-login-logo": "Tekst onder Maatwerk Aanmeld Logo", + "text-below-custom-login-logo": "Tekst onder Maatwerk Login Logo", "automatic-linked-url-schemes": "Maatwerk URL-schema's die automatisch klikbaar zouden moeten zijn. Een URL per regel.", "username": "Gebruikersnaam", "import-usernames": "Importeer Gebruikersnamen", - "view-it": "Toon het", + "view-it": "Bekijk het", "warn-list-archived": "Let op: deze kaart zit in gearchiveerde lijst", "watch": "Bekijk", "watching": "Bekijken", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Sta E-mailadres wijzigingen toe", "accounts-allowUserNameChange": "Sta Gebruikersnaam wijzigingen toe", "tableVisibilityMode-allowPrivateOnly": "Zichtbaarheid borden: Sta alleen privé borden toe", - "tableVisibilityMode": "Zichtbaarheid borden", + "tableVisibilityMode" : "Zichtbaarheid borden", "createdAt": "Aangemaakt op", "modifiedAt": "Gewijzigd op", "verified": "Geverifieerd", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Wijzig ontvangstdatum", "editCardEndDatePopup-title": "Wijzig einddatum", "setCardColorPopup-title": "Stel kleur in", - "setSelectionColorPopup-title": "Stel geselecteerde kleur in", "setCardActionsColorPopup-title": "Kies een kleur", "setSwimlaneColorPopup-title": "Kies een kleur", "setListColorPopup-title": "Kies een kleur", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Alle lijsten, kaarten, labels en activiteiten zullen worden verwijderd en je kunt de bordinhoud niet terughalen. Er is geen herstelmogelijkheid.", "boardDeletePopup-title": "Bord verwijderen?", "delete-board": "Verwijder bord", - "delete-all-notifications": "Verwijder Alle Notificaties", - "delete-all-notifications-confirm": "Weet je zeker dat je alle notificaties wilt verwijderen? Er is geen herstelmogelijkheid.", "delete-duplicate-lists": "Verwijder Dubbele Lijsten", "delete-duplicate-lists-confirm": "Weet je het zeker? Alle dubbele lijsten die dezelfde naam hebben en geen kaarten bevatten worden verwijderd.", "default-subtasks-board": "Subtaken voor __board__ bord", @@ -845,7 +808,7 @@ "r-removed-from": "Verwijderd van", "r-the-board": "het bord", "r-list": "lijst", - "set-filter": "Stel Filter In", + "set-filter": "Definieer Filter", "r-moved-to": "verplaatst naar", "r-moved-from": "verplaatst van", "r-archived": "Verplaatst naar Archief", @@ -942,13 +905,6 @@ "authentication-method": "Authenticatiemethode", "authentication-type": "Authenticatietype", "custom-product-name": "Eigen Productnaam", - "custom-head-tags-enabled": "Schakel maatwerk head tags in", - "custom-head-meta-tags": "Maatwerk meta tags (HTML)", - "custom-head-link-tags": "Maatwerk link tags (HTML)", - "custom-manifest-enabled": "Schakel maatwerk web manifest in", - "custom-head-manifest-content": "Maatwerk web manifest inhoud (JSON)", - "custom-assetlinks-enabled": "Schakel maatwerk assetlinks.json in", - "custom-assetlinks-content": "Maatwerk assetlinks.json inhoud (JSON)", "layout": "Lay-out", "hide-logo": "Verberg Logo", "hide-card-counter-list": "Verberg kaarten teller lijst op alle borden", @@ -956,7 +912,7 @@ "add-custom-html-after-body-start": "Voeg eigen HTML toe na <body> start", "add-custom-html-before-body-end": "Voeg eigen HTML toe voor </body> einde", "error-undefined": "Er is iets misgegaan", - "error-ldap-login": "Er is een fout opgetreden tijdens het aanmelden", + "error-ldap-login": "Er is een fout opgetreden tijdens het inloggen", "display-authentication-method": "Toon Authenticatiemethode", "oidc-button-text": "Pas de OIDC-button tekst aan.", "default-authentication-method": "Standaard Authenticatiemethode", @@ -967,7 +923,7 @@ "people-number": "Het aantal gebruikers is:", "swimlaneDeletePopup-title": "Swimlane verwijderen?", "swimlane-delete-pop": "Alle acties zullen verwijderd worden van de activiteiten feed en je kunt de swimlane niet terughalen. Er is geen herstelmogelijkheid.", - "restore-all": "Herstel alles", + "restore-all": "Haal alles terug", "delete-all": "Verwijder alles", "loading": "Laden, even geduld.", "previous_as": "laatste keer was", @@ -979,8 +935,6 @@ "a-endAt": "einddatum gewijzigd naar", "a-startAt": "begindatum gewijzigd naar", "a-receivedAt": "ontvangstdatum gewijzigd naar", - "above-selected-card": "Boven geselecteerde kaart", - "below-selected-card": "Onder geselecteerde kaart", "almostdue": "huidige vervaldatum %s nadert", "pastdue": "huidige vervaldatum %s is verlopen", "duenow": "huidige vervaldatum %s is vandaag", @@ -989,7 +943,7 @@ "act-almostdue": "wil je herinneren aan het naderen van de huidige vervaldatum (__timeValue__) van __card__", "act-pastdue": "wil je herinneren aan het verlopen van de huidige vervaldatum (__timeValue__) van __card__", "act-duenow": "wil je herinneren aan het vandaag verlopen van de huidige vervaldatum (__timeValue__) van __card__", - "act-atUserComment": "heeft jou genoemd op kaart __card__: __comment__ op lijst __list__ op swimlane __swimlane__ van bord __board__", + "act-atUserComment": "Je werd genoemd in [__board__] __list__/__card__", "delete-user-confirm-popup": "Weet je zeker dat je dit account wilt verwijderen? Er is geen herstelmogelijkheid.", "delete-team-confirm-popup": "Weet je zeker dat je dit team wilt verwijderen? Er is geen herstelmogelijkheid.", "delete-org-confirm-popup": "Weet je zeker dat je deze organisatie wilt verwijderen? Er is geen herstelmogelijkheid.", @@ -1010,14 +964,13 @@ "newUserPopup-title": "Nieuwe gebruiker", "notifications": "Meldingen", "help": "Help", - "view-all": "Toon alles", + "view-all": "Bekijk alles", "filter-by-unread": "Filter op Ongelezen", "mark-all-as-read": "Markeer alles als gelezen", - "mark-all-as-unread": "Markeer alles als ongelezen", "remove-all-read": "verwijder alle gelezen", "allow-rename": "Sta Hernoemen toe", "allowRenamePopup-title": "Sta Hernoemen toe", - "start-day-of-week": "Stel eerste dag van de week in", + "start-day-of-week": "Stel eerste dag van de week in op", "monday": "Maandag", "tuesday": "Dinsdag", "wednesday": "Woensdag", @@ -1048,10 +1001,6 @@ "person": "Persoon", "my-cards": "Mijn kaarten", "card": "Kaart", - "today": "Vandaag", - "day": "Dag", - "week": "Week", - "month": "Maand", "list": "Lijst", "board": "Bord", "context-separator": "/", @@ -1064,8 +1013,8 @@ "myCardsSortChange-choice-board": "Naar bord", "myCardsSortChange-choice-dueat": "Naar vervaldatum", "dueCards-title": "Achterstallige kaarten", - "dueCardsViewChange-title": "Achterstallige Kaarten Overzicht", - "dueCardsViewChangePopup-title": "Achterstallige Kaarten Overzicht", + "dueCardsViewChange-title": "Achterstallige kaart view", + "dueCardsViewChangePopup-title": "Achterstallige kaart view", "dueCardsViewChange-choice-me": "Mij", "dueCardsViewChange-choice-all": "Alle gebruikers", "dueCardsViewChange-choice-all-description": "Toon incomplete kaarten met een *achterstallige* datum van borden waarvoor de gebruiker toegang heeft.", @@ -1341,18 +1290,13 @@ "hideAllChecklistItems": "Verberg alle checklist items", "support": "Ondersteuning", "supportPopup-title": "Ondersteuning", - "support-page-enabled": "Ondersteuningspagina ingeschakeld", - "support-info-not-added-yet": "Ondersteuningsinformatie is nog niet toegevoegd", - "support-info-only-for-logged-in-users": "Ondersteuningsinformatie is alleen voor ingelogde gebruikers.", - "support-title": "Ondersteuningstitel", - "support-content": "Ondersteuningsinhoud", "accessibility": "Toegankelijkheid", "accessibility-page-enabled": "Toegankelijkheidspagina ingeschakeld", "accessibility-info-not-added-yet": "Toegankelijkheidsinformatie is nog niet toegevoegd", "accessibility-title": "Toegankelijkheidstitel", "accessibility-content": "Toegankelijkheid inhoud", "accounts-lockout-settings": "Brute Force Beschermingsinstellingen", - "accounts-lockout-info": "Deze instellingen bepalen hoe aanmeldpogingen worden beschermd tegen brute force aanvallen.", + "accounts-lockout-info": "Deze instellingen bepalen hoe inlogpogingen worden beschermd tegen brute force aanvallen.", "accounts-lockout-known-users": "Instellingen voor bekende gebruikers (juiste gebruikersnaam, fout wachtwoord)", "accounts-lockout-unknown-users": "Instellingen voor onbekende gebruikers (onbekende gebruikersnaam)", "accounts-lockout-failures-before": "Mislukkingen voor blokkeren", @@ -1360,7 +1304,7 @@ "accounts-lockout-failure-window": "Mislukkingen periode (seconden)", "accounts-lockout-settings-updated": "Brute Force instellingen zijn bijgewerkt", "accounts-lockout-locked-users": "Geblokkeerde Gebruikers", - "accounts-lockout-locked-users-info": "Geblokkeerde gebruikers door te veel mislukte aanmeldpogingen", + "accounts-lockout-locked-users-info": "Geblokkeerde gebruikers door te veel mislukte inlogpogingen", "accounts-lockout-no-locked-users": "Er zijn momenteel geen geblokkeerde gebruikers", "accounts-lockout-failed-attempts": "Mislukte Pogingen", "accounts-lockout-remaining-time": "Resterende Tijd", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Geplande taak verwijderen gelukt", "cron-job-pause-failed": "Geplande taak pauzeren mislukt", "cron-job-paused": "Geplande taak pauzeren gelukt", - "cron-job-resume-failed": "Geplande taak hervatten mislukt", - "cron-job-resumed": "Geplande taak hervatten gelukt", - "cron-job-start-failed": "Geplande taak starten mislukt", - "cron-job-started": "Geplande taak starten gelukt", - "cron-migration-errors": "Migratie Fouten", - "cron-migration-warnings": "Migratie Waarschuwingen", - "cron-no-errors": "Geen fouten om te laten zien", - "cron-error-severity": "Ernst", - "cron-error-time": "Tijd", - "cron-error-message": "Foutmelding", - "cron-error-details": "Details", - "cron-clear-errors": "Wis Alle Fouten", - "cron-retry-failed": "Probeer Mislukte Migraties Opniew", - "cron-resume-paused": "Hervat Gepauzeerde Migraties", - "cron-errors-cleared": "Alle fouten wissen gelukt", - "cron-no-failed-migrations": "Geen mislukte migraties om opnieuw te proberen", - "cron-no-paused-migrations": "Geen gepauzeerde migraties om te hervatten", - "cron-migrations-resumed": "Migraties hervatten gelukt", - "cron-migrations-retried": "Mislukte migraties opnieuw proberen gelukt", - "complete": "Voltooid", - "idle": "Inactief", "filesystem-path-description": "Hoofdmap voor bestandsopslag", "gridfs-enabled": "GridFS Ingeschakeld", "gridfs-enabled-description": "Gebruik MongoDB GridFS voor bestandsopslag", - "all-migrations": "Alle Migraties", - "select-migration": "Selecteer Migratie", - "start": "Begin", - "pause": "Pauzeer", - "stop": "Stop", - "migration-starting": "Migraties starten...", - "migration-pausing": "Migraties pauzeren...", - "migration-stopping": "Migraties stoppen...", "migration-pause-failed": "Migraties pauzeren mislukt", "migration-paused": "Migraties pauzeren gelukt", "migration-progress": "Migratie Voortgang", "migration-start-failed": "Migraties starten mislukt", "migration-started": "Migraties starten gelukt", - "migration-not-needed": "Geen migratie nodig", "migration-status": "Migratie Status", "migration-stop-confirm": "Weet je zeker dat je alle migraties wilt stoppen?", "migration-stop-failed": "Migraties stoppen mislukt", @@ -1490,73 +1404,7 @@ "back-to-settings": "Terug naar Instellingen", "board-id": "Bord ID", "board-migration": "Bord Migratie", - "board-migrations": "Bord Migraties", "card-show-lists-on-minicard": "Toon Lijsten op Minikaart", - "comprehensive-board-migration": "Uitgebreide Bord Migratie", - "comprehensive-board-migration-description": "Voert uitgebreide controles en reparaties uit voor bord data-integriteit, inclusief lijst sortering, kaart posities en swimlane-structuur.", - "delete-duplicate-empty-lists-migration": "Verwijder Dubbele Lege Lijsten", - "delete-duplicate-empty-lists-migration-description": "Verwijderd veilig lege dubbele lijsten. Verwijderd alleen lijsten die geen kaarten bevatten EN waar een andere lijst bestaat met dezelfde titel die wel kaarten bevat.", - "lost-cards": "Verloren Kaarten", - "lost-cards-list": "Herstelde Items", - "restore-lost-cards-migration": "Herstel Verloren Kaarten", - "restore-lost-cards-migration-description": "Vind en herstel kaarten en lijsten met missende swimlane-ID of list-ID. Hier wordt een 'Verloren Kaarten'-swimlane gemaakt om alles verloren items weer zichtbaar te maken.", - "restore-all-archived-migration": "Herstel Alles ui Archief", - "restore-all-archived-migration-description": "Herstel alle ge-archiveerde swimlanes, lijsten en kaarten. Hierbij worden automatisch de missende swimlane-ID of lijs-ID gerapareerd om de items weer zichtbaar te maken.", - "fix-missing-lists-migration": "Repareer Missende Lijsten", - "fix-missing-lists-migration-description": "Detecteer en repareer missende of corrupte lijsten in de bordstructuur.", - "fix-avatar-urls-migration": "Repareer Avatar URL's", - "fix-avatar-urls-migration-description": "Werkt de avatar URL's van de bord-leden bij zodat de juiste opslagmethode gebruikt wordt en het repareert defecte avatar verwijzingen.", - "fix-all-file-urls-migration": "Repareer alle bestand URL's", - "fix-all-file-urls-migration-description": "Werkt alle bestandsbijlagen URL's op dit bord bij naar de juiste opslagmethode en repareert defecte bestandsverwijzingen.", - "migration-needed": "Migratie Nodig", - "migration-complete": "Voltooid", - "migration-running": "In Uitvoering...", - "migration-successful": "Migratie succesvol uitgevoerd", - "migration-failed": "Migratie mislukt", - "migrations": "Migraties", - "migrations-admin-only": "Alleen bord-beherders kunnen migraties uitvoeren", - "migrations-description": "Voer data-integriteits controles en reparaties uit op dit bord. Elke migratie kan afzonderlijk uitgevoerd worden.", - "no-issues-found": "Geen problemen gevonden", - "run-migration": "Voer Migratie Uit", - "run-comprehensive-migration-confirm": "Dit voert een uitgebreide migratie uit en controleert en repareert de data-integriteit op dit bord. Dit kan even duren. Doorgaan?", - "run-delete-duplicate-empty-lists-migration-confirm": "Dit converteert eventuele gedeelde lijsten naar lijsten per swimlane waarna lege lijsten verwijderd worden die dubbel zijn met een lijst met dezelfde titel en wel kaarten bevatten. Alleen echt dubbele lege lijsten worden verwijderd. Doorgaan?", - "run-restore-lost-cards-migration-confirm": "Dit creëert een 'Verloren Kaarten' swimlane en hierin worden kaarten en lijsten hersteld met een missende swimlane-ID of lijst-ID. Dit heeft alleen gevolgen voor niet gearchiveerde items. Doorgaan?", - "run-restore-all-archived-migration-confirm": "Dit herstelt alle gearchiveerde swimlanes, lijsten en kaarten waardoor ze weer zichtbaar worden. Items met een missende ID zulle automatisch gerepareerd worden. Doorgaan?", - "run-fix-missing-lists-migration-confirm": "Dit detecteert en repareert missende of corrupte lijsten in de bord-structuur. Doorgaan?", - "run-fix-avatar-urls-migration-confirm": "Dit werkt alle avatar URL's van de bord-leden bij naar de juiste opslagmethode. Doorgaan?", - "run-fix-all-file-urls-migration-confirm": "dit werkt alle bestandsbijlagen URL's bij naar de juiste opslagmethode. Doorgaan?", - "restore-lost-cards-nothing-to-restore": "Geen verloren swinmlanes, lijsten of kaarten om te herstellen", - - "migration-progress-title": "Bord Migratie in Uitvoering", - "migration-progress-overall": "Algehele Voortgang", - "migration-progress-current-step": "Huidige Stap", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Wacht tot we jouw bord gemigreerd hebben naar de actuele structuur...", - "steps": "stappen", - "view": "Toon", - "has-swimlanes": "Heeft Swimlanes", - - "step-analyze-board-structure": "Bordstructuur Analyseren", - "step-fix-orphaned-cards": "Repareer Verweesde Kaarten", - "step-convert-shared-lists": "Converteer Gedeelde Lijsten", - "step-ensure-per-swimlane-lists": "Maak Per-Swimlane Lijsten", - "step-validate-migration": "Valideer Migratie", - "step-fix-avatar-urls": "Repareer Avatar URL's", - "step-fix-attachment-urls": "Repareer Bijlage URL's", - "step-analyze-lists": "Analyseer Lijsten", - "step-create-missing-lists": "Maak Missende Lijsten", - "step-update-cards": "Werk Kaarten Bij", - "step-finalize": "Beëindig", - "step-delete-duplicate-empty-lists": "Verwijder Dubbele Lege Lijsten", - "step-ensure-lost-cards-swimlane": "Maak Verloren Kaarten Swimlane", - "step-restore-lists": "Herstel Lijsten", - "step-restore-cards": "Herstel Kaarten", - "step-restore-swimlanes": "Herstel Swimlanes", - "step-fix-missing-ids": "Herstel Missende ID's", - "step-scan-users": "Bord-leden avatars controleren", - "step-scan-files": "Bord bestandsbijlagen controleren", - "step-fix-file-urls": "Herstel bestand URL's", "cleanup": "Opschonen", "cleanup-old-jobs": "Schoon Oude Taken Op", "completed": "Afgewerkt", @@ -1637,7 +1485,6 @@ "schedule": "Plannen", "search-boards-or-operations": "Zoek borden of acties...", "show-list-on-minicard": "Toon Lijst op Minikaart", - "showChecklistAtMinicard": "Toon Checklist op Minikaart", "showing": "Tonen", "start-test-operation": "Start Test Actie", "start-time": "Starttijd", @@ -1650,37 +1497,7 @@ "total-size": "Totale Grootte", "unmigrated-boards": "Niet Gemigreerde Borden", "weight": "Gewicht", - "cron": "Planning", - "current-step": "Huidige Stap", - "otp": "OTP-code", - "create-account": "Account Aanmaken", - "already-account": "Heb je al een account? Log in", - "available-repositories": "Beschikbare Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repositorynaam", - "size-bytes": "Grootte (bytes)", - "last-modified": "Laatst Gewijzigd", - "no-repositories": "Geen repositories gevonden", - "create-repository": "Repository Aanmaken", - "upload-repository": "Repository Uploaden/Bijwerken", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Log in om repositories te uploaden", - "account-locked": "Account tijdelijk geblokkeerd door te veel mislukte aanmeldpogingen. Probeer het later nog eens.", - "otp-required": "OTP-code noodzakelijk", - "invalid-credentials": "Ongeldige gebruikersnaam of wachtwoord", - "username-password-required": "Gebruikersnaam en wachtwoord zijn noodzakelijk", - "password-mismatch": "Wachtwoorden komen niet overeen", - "username-too-short": "Gebruikersnaam moet minimaal 3 tekens lang zijn", - "user-exists": "Gebruiker bestaat al", - "account-created": "Account aangemaakt! Je kunt nu inloggen.", - "account-creation-failed": "Account aanmaken mislukt", - "login": "Aanmelden", - "confirm": "Bevestig", - "error": "Fout", - "file": "Bestand", - "log": "Log", - "logout": "Afmelden", - "server": "Server", - "protocol": "Protocol" + "idle": "Inactief", + "complete": "Voltooid", + "cron": "Planning" } diff --git a/imports/i18n/data/oc.i18n.json b/imports/i18n/data/oc.i18n.json index 6d0e70f9a..5da5ae2bc 100644 --- a/imports/i18n/data/oc.i18n.json +++ b/imports/i18n/data/oc.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Modèls", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Apondre una pèça joncha", @@ -98,7 +86,6 @@ "add-card": "Apondre una carta", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Apondre una tièra", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Apondre un participant", "added": "Apondut lo", - "addMemberPopup-title": "Apondre un participant", + "addMemberPopup-title": "Participants", "memberPopup-title": "Paramètres dels participants", "admin": "Administartor", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "As lo drech de legir e modificar las cartas, tirar de participants, e modificar las opcions del tablèu.", "admin-announcement": "Anóncia", "admin-announcement-active": "Activar l'anóncia globala", "admin-announcement-title": "Anóncia de l'administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s estèla", "board-not-found": "Tablèu pas trapat", "board-private-info": "Aqueste tablèu serà <strong>privat</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Cambiar las permissions", "change-settings": "Cambiar los paramètres", "changeAvatarPopup-title": "Cambiar la fòto", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Cambiar la lenga", "changePasswordPopup-title": "Cambiar lo mot de Santa-Clara", "changePermissionsPopup-title": "Cambiar las permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Escrire un comentari", "comment-only": "Comment only", "comment-only-desc": "Comentari sus las cartas solament.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "Pas cap de comentari", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Podèts pas veire ni los comentaris ni las activitats", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Ordenator", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Còpia del ligam de la carta", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Ligam de la carta", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Títol de la primièra carta\", \"description\":\"Descripcion de la primièra carta\"}, {\"title\":\"Títol de la segonda carta\",\"description\":\"Descripcion de la segonda carta\"},{\"title\":\"Títol de la darrièra carta\",\"description\":\"Descripcion de la darrièra carta\"} ]", "create": "Crear", "createBoardPopup-title": "Crear un tablèu", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Importar un tablèu", "createLabelPopup-title": "Crear una etiqueta", "createCustomField": "Crear un camp", @@ -385,7 +358,7 @@ "date": "Data", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Refusar", "default-avatar": "Fòto per defaut", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Tièras", "swimlanes": "Corredor", - "calendar": "Calendièr", - "gantt": "Gantt", "log-out": "Desconnexion", "log-in": "Connexion", "loginPopup-title": "Connexion", "memberMenuPopup-title": "Paramètres dels participants", - "grey-icons": "Grey Icons", "members": "Participants", "menu": "Menut", "move-selection": "Bolegar la seleccion", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Bolegar cap al bas", "moveCardToTop-title": "Bolegar cap al naut", "moveSelectionPopup-title": "Bolegar la seleccion", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-seleccion", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "Pas brica de resultat", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verificat", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Color seleccionada", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Causir una color", "setSwimlaneColorPopup-title": "Causir una color", "setListColorPopup-title": "Causir una color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Suprimir lo tablèu ?", "delete-board": "Tablèu suprimit", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Carta", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Temps", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Debuta", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/or_IN.i18n.json b/imports/i18n/data/or_IN.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/or_IN.i18n.json +++ b/imports/i18n/data/or_IN.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/pa.i18n.json b/imports/i18n/data/pa.i18n.json index b2d0fb039..1ab8eeaa8 100644 --- a/imports/i18n/data/pa.i18n.json +++ b/imports/i18n/data/pa.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/pl-PL.i18n.json b/imports/i18n/data/pl-PL.i18n.json index ef7bbd632..641328e6d 100644 --- a/imports/i18n/data/pl-PL.i18n.json +++ b/imports/i18n/data/pl-PL.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "usunął komentarz %s", "activity-receivedDate": "zmienił datę otrzymania na %s z %s", "activity-startDate": "zmienił datę rozpoczęcia na %s z %s", - "allboards.starred": "Starred", - "allboards.templates": "Szablony", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "zmienił datę wykonania na %s z %s", "activity-endDate": "zmienił datę zakończenia na %s z %s", "add-attachment": "Dodaj załącznik", @@ -98,7 +86,6 @@ "add-card": "Dodaj kartę", "add-card-to-top-of-list": "Dodaj kartę na początku listy", "add-card-to-bottom-of-list": "Dodaj kartę na końcu listy", - "addListPopup-title": "Dodaj listę", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Dodaj po liście", "add-members": "Dodaj użytkowników", "added": "Dodane", - "addMemberPopup-title": "Dodaj użytkowników", + "addMemberPopup-title": "Użytkownicy", "memberPopup-title": "Ustawienia użytkowników", "admin": "Administrator", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Może widzieć i edytować karty, usuwać użytkowników oraz zmieniać ustawienia tablicy.", "admin-announcement": "Ogłoszenie", "admin-announcement-active": "Włącz ogłoszenie systemowe", "admin-announcement-title": "Ogłoszenie od administratora", @@ -167,16 +154,12 @@ "board-background-image-url": "Adres obrazka tła", "add-background-image": "Dodaj obraz tła", "remove-background-image": "Usuń obraz tła", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s odznaczeń", "board-not-found": "Nie znaleziono tablicy", "board-private-info": "Ta tablica będzie <strong>prywatna</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Zmień uprawnienia", "change-settings": "Zmień ustawienia", "changeAvatarPopup-title": "Zmień avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Zmień język", "changePasswordPopup-title": "Zmień hasło", "changePermissionsPopup-title": "Zmień uprawnienia", @@ -335,16 +316,10 @@ "comment-placeholder": "Dodaj komentarz", "comment-only": "Tylko komentowanie", "comment-only-desc": "Może tylko komentować w kartach.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Czy na pewno chcesz usunąć komentarz?", "deleteCommentPopup-title": "Usunąć komentarz?", "no-comments": "Bez komentarzy", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Nie widzi komentarzy i aktywności.", "worker": "Pracownik", "worker-desc": "Możesz tylko przenieść karty, przypisać je do siebie i na nich komentować.", "computer": "Komputera", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Skopiuj łącze karty do schowka", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Podepnij kartę", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Tytuł pierwszej karty\", \"description\":\"Opis pierwszej karty\"}, {\"title\":\"Tytuł drugiej karty\",\"description\":\"Opis drugiej karty\"},{\"title\":\"Tytuł ostatniej karty\",\"description\":\"Opis ostatniej karty\"} ]", "create": "Utwórz", "createBoardPopup-title": "Utwórz tablicę", - "createTemplateContainerPopup-title": "Dodaj Kontener Szablonów", "chooseBoardSourcePopup-title": "Import tablicy", "createLabelPopup-title": "Utwórz etykietę", "createCustomField": "Utwórz pole", @@ -385,7 +358,7 @@ "date": "Data", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Odrzuć", "default-avatar": "Domyślny avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Możesz przenieść listę do Archiwum, a następnie usunąć ją z tablicy i zachować ją w Aktywności.", "lists": "Listy", "swimlanes": "Ścieżki", - "calendar": "Kalendarz", - "gantt": "Wykres Gantta", "log-out": "Wyloguj", "log-in": "Zaloguj", "loginPopup-title": "Zaloguj", "memberMenuPopup-title": "Ustawienia użytkowników", - "grey-icons": "Grey Icons", "members": "Użytkownicy", "menu": "Menu", "move-selection": "Przenieś zaznaczone", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Przenieś na koniec", "moveCardToTop-title": "Przenieś na początek", "moveSelectionPopup-title": "Przenieś zaznaczone", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Wielokrotne zaznaczenie", "multi-selection-label": "Dodaj etykietę do zaznaczenia", "multi-selection-member": "Dodaj użytkownika do zaznaczenia", @@ -587,8 +555,6 @@ "no-results": "Brak wyników", "normal": "Użytkownik standardowy", "normal-desc": "Może widzieć i edytować karty. Nie może zmieniać ustawiań.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Zaproszenie jeszcze niezaakceptowane", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Otrzymuj powiadomienia z tablic, list i kart, które obserwujesz", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Zezwól na zmianę adresu email", "accounts-allowUserNameChange": "Zezwól na zmianę nazwy użytkownika", "tableVisibilityMode-allowPrivateOnly": "Widoczność tablic: Tylko tablice prywatne", - "tableVisibilityMode": "Widoczność tablic", + "tableVisibilityMode" : "Widoczność tablic", "createdAt": "Utworzone o", "modifiedAt": "Zmodyfikowano", "verified": "Zweryfikowane", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Zmień datę przyjęcia", "editCardEndDatePopup-title": "Zmień datę zakończenia", "setCardColorPopup-title": "Ustaw kolor", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Wybierz kolor", "setSwimlaneColorPopup-title": "Wybierz kolor", "setListColorPopup-title": "Wybierz kolor", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Wszystkie listy, etykiety oraz aktywności zostaną usunięte i nie będziesz w stanie przywrócić zawartości tablicy. Tego nie da się cofnąć.", "boardDeletePopup-title": "Usunąć tablicę?", "delete-board": "Usuń tablicę", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Podzadania dla tablicy __board__", @@ -942,13 +905,6 @@ "authentication-method": "Sposób autoryzacji", "authentication-type": "Typ autoryzacji", "custom-product-name": "Niestandardowa nazwa produktu", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Układ strony", "hide-logo": "Ukryj logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "zmienił czas zakończenia na", "a-startAt": "zmienił czas rozpoczęcia na", "a-receivedAt": "zmienił czas przyjęcia zadania na", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "data wykonania %s jest bliska", "pastdue": "data wykonania %s minęła", "duenow": "data wykonania %s przypada dzisiaj", @@ -989,7 +943,7 @@ "act-almostdue": "przypominał o zbliżającej się dacie wykonania (__timeValue__) karty __card__", "act-pastdue": "przypominał o upłynięciu daty wykonania (__timeValue__) karty __card__", "act-duenow": "przypominał o mijającej właśnie dacie wykonania (__timeValue__) karty __card__", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Zostałeś wspomniany w [__board] __list__/__card__", "delete-user-confirm-popup": "Czy jesteś pewien, że chcesz usunąć te konto? Nie można tego wycofać.", "delete-team-confirm-popup": "Czy na pewno chcesz usunąć ten zespół? Operacji nie można cofnąć.", "delete-org-confirm-popup": "Czy na pewno chcesz usunąć tą organizację? Operacji nie można cofnąć.", @@ -1013,7 +967,6 @@ "view-all": "Wyświetl wszystko", "filter-by-unread": "Filtruj nieprzeczytane", "mark-all-as-read": "Zaznacz wszystkie jako przeczytane", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Usuń wszystkie przeczytane", "allow-rename": "Zezwól na zmianę nazwy", "allowRenamePopup-title": "Zezwól na zmianę nazwy", @@ -1048,10 +1001,6 @@ "person": "Osoba", "my-cards": "Moje karty", "card": "karty", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Lista", "board": "Tablica", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Czas", - "cron-error-message": "Error Message", - "cron-error-details": "Szczegóły", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Rozpoczęcie", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Szczegóły", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "ukończona", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Potwierdź", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/pl.i18n.json b/imports/i18n/data/pl.i18n.json index 91057b936..7ae12a506 100644 --- a/imports/i18n/data/pl.i18n.json +++ b/imports/i18n/data/pl.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "usunięto komentarz %s", "activity-receivedDate": "zmienił datę otrzymania na %s z %s", "activity-startDate": "zmienił datę rozpoczęcia na %s z %s", - "allboards.starred": "Starred", - "allboards.templates": "Szablony", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "zmienił datę wykonania na %s z %s", "activity-endDate": "zmienił datę zakończenia na %s z %s", "add-attachment": "Dodaj załącznik", @@ -98,7 +86,6 @@ "add-card": "Dodaj kartę", "add-card-to-top-of-list": "Dodaj kartę na początku listy", "add-card-to-bottom-of-list": "Dodaj kartę na końcu listy", - "addListPopup-title": "Dodaj listę", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Dodaj po liście", "add-members": "Dodaj użytkowników", "added": "Dodane", - "addMemberPopup-title": "Dodaj użytkowników", + "addMemberPopup-title": "Użytkownicy", "memberPopup-title": "Ustawienia użytkowników", "admin": "Administrator", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Może widzieć i edytować karty, usuwać użytkowników oraz zmieniać ustawienia tablicy.", "admin-announcement": "Ogłoszenie", "admin-announcement-active": "Włącz ogłoszenie systemowe", "admin-announcement-title": "Ogłoszenie od administratora", @@ -167,16 +154,12 @@ "board-background-image-url": "Adres obrazka tła", "add-background-image": "Dodaj obraz tła", "remove-background-image": "Usuń obraz tła", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Wyświetlaj awatary członków tablic", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s odznaczeń", "board-not-found": "Nie znaleziono tablicy", "board-private-info": "Ta tablica będzie <strong>prywatna</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Zmień uprawnienia", "change-settings": "Zmień ustawienia", "changeAvatarPopup-title": "Zmień avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Zmień język", "changePasswordPopup-title": "Zmień hasło", "changePermissionsPopup-title": "Zmień uprawnienia", @@ -335,16 +316,10 @@ "comment-placeholder": "Dodaj komentarz", "comment-only": "Tylko komentowanie", "comment-only-desc": "Może tylko komentować w kartach.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Czy na pewno chcesz usunąć komentarz?", "deleteCommentPopup-title": "Usunąć komentarz?", "no-comments": "Bez komentarzy", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Nie widzi komentarzy i aktywności.", "worker": "Pracownik", "worker-desc": "Możesz tylko przenieść karty, przypisać je do siebie i na nich komentować.", "computer": "Komputera", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Skopiuj łącze karty do schowka", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Podepnij kartę", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Tytuł pierwszej karty\", \"description\":\"Opis pierwszej karty\"}, {\"title\":\"Tytuł drugiej karty\",\"description\":\"Opis drugiej karty\"},{\"title\":\"Tytuł ostatniej karty\",\"description\":\"Opis ostatniej karty\"} ]", "create": "Utwórz", "createBoardPopup-title": "Utwórz tablicę", - "createTemplateContainerPopup-title": "Dodaj Kontener Szablonów", "chooseBoardSourcePopup-title": "Import tablicy", "createLabelPopup-title": "Utwórz etykietę", "createCustomField": "Utwórz pole", @@ -385,7 +358,7 @@ "date": "Data", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Odrzuć", "default-avatar": "Domyślny avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Możesz przenieść listę do Archiwum, a następnie usunąć ją z tablicy i zachować ją w Aktywności.", "lists": "Listy", "swimlanes": "Ścieżki", - "calendar": "Kalendarz", - "gantt": "Wykres Gantta", "log-out": "Wyloguj", "log-in": "Zaloguj", "loginPopup-title": "Zaloguj", "memberMenuPopup-title": "Ustawienia użytkowników", - "grey-icons": "Grey Icons", "members": "Użytkownicy", "menu": "Menu", "move-selection": "Przenieś zaznaczone", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Przenieś na koniec", "moveCardToTop-title": "Przenieś na początek", "moveSelectionPopup-title": "Przenieś zaznaczone", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Wielokrotne zaznaczenie", "multi-selection-label": "Dodaj etykietę do zaznaczenia", "multi-selection-member": "Dodaj użytkownika do zaznaczenia", @@ -587,8 +555,6 @@ "no-results": "Brak wyników", "normal": "Użytkownik standardowy", "normal-desc": "Może widzieć i edytować karty. Nie może zmieniać ustawiań.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Zaproszenie jeszcze niezaakceptowane", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Otrzymuj powiadomienia z tablic, list i kart, które obserwujesz", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Zezwól na zmianę adresu email", "accounts-allowUserNameChange": "Zezwól na zmianę nazwy użytkownika", "tableVisibilityMode-allowPrivateOnly": "Widoczność tablic: Tylko tablice prywatne", - "tableVisibilityMode": "Widoczność tablic", + "tableVisibilityMode" : "Widoczność tablic", "createdAt": "Utworzone o", "modifiedAt": "Zmodyfikowano", "verified": "Zweryfikowane", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Zmień datę przyjęcia", "editCardEndDatePopup-title": "Zmień datę zakończenia", "setCardColorPopup-title": "Ustaw kolor", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Wybierz kolor", "setSwimlaneColorPopup-title": "Wybierz kolor", "setListColorPopup-title": "Wybierz kolor", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Wszystkie listy, etykiety oraz aktywności zostaną usunięte i nie będziesz w stanie przywrócić zawartości tablicy. Tego nie da się cofnąć.", "boardDeletePopup-title": "Usunąć tablicę?", "delete-board": "Usuń tablicę", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Podzadania dla tablicy __board__", @@ -942,13 +905,6 @@ "authentication-method": "Sposób autoryzacji", "authentication-type": "Typ autoryzacji", "custom-product-name": "Niestandardowa nazwa produktu", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Układ strony", "hide-logo": "Ukryj logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "zmienił czas zakończenia na", "a-startAt": "zmienił czas rozpoczęcia na", "a-receivedAt": "zmienił czas przyjęcia zadania na", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "data wykonania %s jest bliska", "pastdue": "data wykonania %s minęła", "duenow": "data wykonania %s przypada dzisiaj", @@ -989,7 +943,7 @@ "act-almostdue": "przypominał o zbliżającej się dacie wykonania (__timeValue__) karty __card__", "act-pastdue": "przypominał o upłynięciu daty wykonania (__timeValue__) karty __card__", "act-duenow": "przypominał o mijającej właśnie dacie wykonania (__timeValue__) karty __card__", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Zostałeś wspomniany w [__board] __list__/__card__", "delete-user-confirm-popup": "Czy jesteś pewien, że chcesz usunąć te konto? Nie można tego wycofać.", "delete-team-confirm-popup": "Czy na pewno chcesz usunąć ten zespół? Operacji nie można cofnąć.", "delete-org-confirm-popup": "Czy na pewno chcesz usunąć tą organizację? Operacji nie można cofnąć.", @@ -1013,7 +967,6 @@ "view-all": "Wyświetl wszystko", "filter-by-unread": "Filtruj nieprzeczytane", "mark-all-as-read": "Zaznacz wszystkie jako przeczytane", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Usuń wszystkie przeczytane", "allow-rename": "Zezwól na zmianę nazwy", "allowRenamePopup-title": "Zezwól na zmianę nazwy", @@ -1048,10 +1001,6 @@ "person": "Osoba", "my-cards": "Moje karty", "card": "karty", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Lista", "board": "Tablica", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Czas", - "cron-error-message": "Error Message", - "cron-error-details": "Szczegóły", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Rozpoczęcie", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Szczegóły", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "ukończona", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Potwierdź", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/pt-BR.i18n.json b/imports/i18n/data/pt-BR.i18n.json index d07486e08..5a135f543 100644 --- a/imports/i18n/data/pt-BR.i18n.json +++ b/imports/i18n/data/pt-BR.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "comentário excluído %s", "activity-receivedDate": "editou recebido para %s de %s", "activity-startDate": "editou data início para %s de %s", - "allboards.starred": "Favoritado", - "allboards.templates": "Modelos", - "allboards.remaining": "Restante", - "allboards.workspaces": "Áreas de trabalho", - "allboards.add-workspace": "Adicionar Área de trabalho", - "allboards.add-workspace-prompt": "Nome da Área de trabalho", - "allboards.add-subworkspace": "Adicionar Subárea de trabalho", - "allboards.add-subworkspace-prompt": "Nome da Subárea de trabalho", - "allboards.edit-workspace": "Editar Área de trabalho", - "allboards.edit-workspace-name": "Nome da Área de trabalho", - "allboards.edit-workspace-icon": "Ícone da Área de trabalho (markdown)", - "multi-selection-active": "Clique nas caixas de seleção para selecionar os quadros", "activity-dueDate": "editou prazo final para %s de %s", "activity-endDate": "editou concluído para %s de %s", "add-attachment": "Adicionar Anexos", @@ -98,7 +86,6 @@ "add-card": "Adicionar Cartão", "add-card-to-top-of-list": "Adicionar Cartão no Topo da Lista", "add-card-to-bottom-of-list": "Adicionar Cartão no Final da Lista", - "addListPopup-title": "Adicionar Lista", "setListWidthPopup-title": "Definir Largura", "set-list-width": "Definir Largura", "set-list-width-value": "Definir Largura Mínima e Máxima (pixeis)", @@ -122,10 +109,10 @@ "add-after-list": "Adicionar Após a Lista", "add-members": "Adicionar Membros", "added": "Criado", - "addMemberPopup-title": "Adicionar Membros", + "addMemberPopup-title": "Membros", "memberPopup-title": "Configurações de Membro", "admin": "Administrador", - "admin-desc": "Pode ver e editar cartões, remover membros e alterar configurações do quadro. Pode ver atividades.", + "admin-desc": "Pode ver e editar cartões, remover membros e alterar configurações do quadro.", "admin-announcement": "Anúncio", "admin-announcement-active": "Anúncio ativo em todo o sistema", "admin-announcement-title": "Anúncio do Administrador", @@ -167,16 +154,12 @@ "board-background-image-url": "URL da Imagem de Fundo", "add-background-image": "Adicionar Imagem de Fundo", "remove-background-image": "Remover Imagem de Fundo", - "show-at-all-boards-page": "Mostrar na página Todos os Quadros", - "board-info-on-my-boards": "Configurações de Todos os Quadros", - "boardInfoOnMyBoardsPopup-title": "Configurações de Todos os Quadros", + "show-at-all-boards-page" : "Mostrar na página Todos os Quadros", + "board-info-on-my-boards" : "Configurações de Todos os Quadros", + "boardInfoOnMyBoardsPopup-title" : "Configurações de Todos os Quadros", "boardInfoOnMyBoards-title": "Configurações de Todos os Quadros", "show-card-counter-per-list": "Mostrar contador de cartões por lista", "show-board_members-avatar": "Mostrar avatar dos membros do quadro", - "board_members": "Todos os membros do quadro", - "card_members": "Todos os membros atuais deste cartão", - "board_assignees": "Todos os administradores de todos os cartões neste quadro", - "card_assignees": "Todos os administradores do cartão atual neste quadro", "board-nb-stars": "%s estrelas", "board-not-found": "Quadro não encontrado", "board-private-info": "Este quadro será <strong>privado</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Alterar permissões", "change-settings": "Alterar configurações", "changeAvatarPopup-title": "Alterar Avatar", - "delete-avatar-confirm": "Tem certeza que deseja excluir este avatar?", - "deleteAvatarPopup-title": "Excluir Avatar?", "changeLanguagePopup-title": "Alterar Idioma", "changePasswordPopup-title": "Alterar Senha", "changePermissionsPopup-title": "Alterar Permissões", @@ -335,16 +316,10 @@ "comment-placeholder": "Escrever Comentário", "comment-only": "Somente comentários", "comment-only-desc": "Pode comentar apenas em cartões.", - "comment-assigned-only": "Somente Comentário Atribuído", - "comment-assigned-only-desc": "Somente cartões atribuídos visível. Pode somente comentar.", "comment-delete": "Você tem certeza que deseja excluir o comentário?", "deleteCommentPopup-title": "Excluir comentário?", "no-comments": "Sem comentários", - "no-comments-desc": "Não pode ver comentários", - "read-only": "Somente Leitura", - "read-only-desc": "Pode somente ver cartões. Não pode editar.", - "read-assigned-only": "Somente Leitura Atribuída", - "read-assigned-only-desc": "Somente cartões atribuídos visível. Não pode editar.", + "no-comments-desc": "Sem visualização de comentários e atividades.", "worker": "Colaborador", "worker-desc": "Pode apenas mover cartões, atribuir-se ao cartão e comentar.", "computer": "Computador", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Você tem certeza que deseja excluir a lista de verificação?", "subtaskDeletePopup-title": "Excluir subtarefa?", "checklistDeletePopup-title": "Excluir lista de verificação?", - "checklistItemDeletePopup-title": "Excluir Item da Lista de Verificação?", "copy-card-link-to-clipboard": "Copiar link do cartão para a área de transferência", "copy-text-to-clipboard": "Copiar texto para a área de transferência", "linkCardPopup-title": "Ligar Cartão", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Título do primeiro cartão\", \"description\":\"Descrição do primeiro cartão\"}, {\"title\":\"Título do segundo cartão\",\"description\":\"Descrição do segundo cartão\"},{\"title\":\"Título do último cartão\",\"description\":\"Descrição do último cartão\"} ]", "create": "Criar", "createBoardPopup-title": "Criar Quadro", - "createTemplateContainerPopup-title": "Adicionar Contêiner de Modelo", "chooseBoardSourcePopup-title": "Importar quadro", "createLabelPopup-title": "Criar Etiqueta", "createCustomField": "Criar campo", @@ -385,7 +358,7 @@ "date": "Data", "date-format": "Formato da Data", "date-format-yyyy-mm-dd": "AAAA-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-AAAA", + "date-format-dd-mm-yyyy": "DD-MM-AAAA", "date-format-mm-dd-yyyy": "MM-DD-AAAA", "decline": "Rejeitar", "default-avatar": "Avatar padrão", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Você pode mover uma lista para o Arquivo morto para removê-la do quadro e preservar a atividade.", "lists": "Listas", "swimlanes": "Raias", - "calendar": "Calendário", - "gantt": "Gantt", "log-out": "Sair", "log-in": "Entrar", "loginPopup-title": "Entrar", "memberMenuPopup-title": "Configurações de Membro", - "grey-icons": "Ícones Cinza", "members": "Membros", "menu": "Menu", "move-selection": "Mover seleção", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Mover para o final", "moveCardToTop-title": "Mover para o topo", "moveSelectionPopup-title": "Mover seleção", - "copySelectionPopup-title": "Copiar seleção", - "selection-color": "Cor da seleção", "multi-selection": "Multi-Seleção", "multi-selection-label": "Definir etiqueta para a seleção", "multi-selection-member": "Definir membro para a seleção", @@ -587,8 +555,6 @@ "no-results": "Nenhum resultado.", "normal": "Normal", "normal-desc": "Pode ver e editar cartões. Não pode alterar configurações.", - "normal-assigned-only": "Somente Normal Atribuído", - "normal-assigned-only-desc": "Somente cartões atribuídos visível. Editar como usuário Normal.", "not-accepted-yet": "Convite ainda não aceito", "notify-participate": "Receba atualizações de todos os cartões que você participa como criador ou membro", "notify-watch": "Receber atualizações de qualquer board, lista ou cards que você estiver observando", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Permitir Mudança de e-mail", "accounts-allowUserNameChange": "Permitir alteração de nome de usuário", "tableVisibilityMode-allowPrivateOnly": "Visibilidade do Quadro: Permitir apenas quadros privados", - "tableVisibilityMode": "Visibilidade do Quadro", + "tableVisibilityMode" : "Visibilidade do Quadro", "createdAt": "Criado em", "modifiedAt": "Modificado em", "verified": "Verificado", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Modificar data de recebimento", "editCardEndDatePopup-title": "Mudar data de conclusão", "setCardColorPopup-title": "Definir cor", - "setSelectionColorPopup-title": "Definir cor da seleção", "setCardActionsColorPopup-title": "Escolha uma cor", "setSwimlaneColorPopup-title": "Escolha uma cor", "setListColorPopup-title": "Escolha uma cor", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Todas as listas, cartões, etiquetas e atividades serão excluídas e você não poderá recuperar o conteúdo do quadro. Não há como desfazer.", "boardDeletePopup-title": "Excluir quadro?", "delete-board": "Excluir quadro", - "delete-all-notifications": "Excluir Todas Notificações", - "delete-all-notifications-confirm": "Você tem certeza que deseja excluir todas notificações? Esta ação não pode ser desfeita.", "delete-duplicate-lists": "Excluir Listas Duplicadas", "delete-duplicate-lists-confirm": "Você tem certeza? Isso vai apagar todas as litas duplicadas que possuem o mesmo nome e que não possuem cartões", "default-subtasks-board": "Subtarefas para quadro __board__", @@ -942,13 +905,6 @@ "authentication-method": "Método de autenticação", "authentication-type": "Tipo de autenticação", "custom-product-name": "Nome Customizado do Produto", - "custom-head-tags-enabled": "Ativar tags de cabeçalho personalizadas", - "custom-head-meta-tags": "Meta tags personalizadas (HTML)", - "custom-head-link-tags": "Tags de link personalizadas (HTML)", - "custom-manifest-enabled": "Ativar manifesto web personalizado", - "custom-head-manifest-content": "Conteúdo personalizado do manifesto da web (JSON)", - "custom-assetlinks-enabled": "Ativar assetlinks.json personalizado", - "custom-assetlinks-content": "Conteúdo personalizado do assetlinks.json (JSON)", "layout": "Layout", "hide-logo": "Esconder Logo", "hide-card-counter-list": "Esconder contador de cartões por lista em Todos os Quadros", @@ -979,8 +935,6 @@ "a-endAt": "hora de conclusão modificada para", "a-startAt": "hora de início modificada para", "a-receivedAt": "hora de recebido modificada para", - "above-selected-card": "Selecionar cartão acima", - "below-selected-card": "Selecionar cartão abaixo", "almostdue": "prazo final atual %s está próximo", "pastdue": "prazo final atual %s venceu", "duenow": "prazo final atual %s é hoje", @@ -989,7 +943,7 @@ "act-almostdue": "está lembrando que o prazo final atual (__timeValue__) do __card__ está próximo", "act-pastdue": "está lembrando que o prazo final atual (__timeValue__) do __card__ venceu", "act-duenow": "está lembrando que o prazo final (__timeValue__) do __card__ é agora", - "act-atUserComment": "mencionou você no __card__: __comment__ da lista __list__ da raia __swimlane__ do quadro __board__", + "act-atUserComment": "Você foi mencionado no [__board__] __list__/__card__", "delete-user-confirm-popup": "Você realmente quer apagar esta conta? Não há como desfazer.", "delete-team-confirm-popup": "Você tem certeza que deseja excluir este time? Não há como desfazer.", "delete-org-confirm-popup": "Você tem certeza que deseja excluir esta organização? Não há como desfazer.", @@ -1013,7 +967,6 @@ "view-all": "Ver tudo", "filter-by-unread": "Filtrar não lidas", "mark-all-as-read": "Marcar todas como lidas", - "mark-all-as-unread": "Marcar todas como não lidas", "remove-all-read": "Remover todas lidas", "allow-rename": "Permitir renomear", "allowRenamePopup-title": "Permitir renomear", @@ -1048,10 +1001,6 @@ "person": "Pessoa", "my-cards": "Meus Cartões", "card": "Cartão", - "today": "Hoje", - "day": "Dia", - "week": "Semana", - "month": "Mês", "list": "Lista", "board": "Quadro", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Esconder todos os itens da lista de verificação", "support": "Suporte", "supportPopup-title": "Suporte", - "support-page-enabled": "Página de suporte ativada", - "support-info-not-added-yet": "As informações de suporte ainda não foram adicionadas.", - "support-info-only-for-logged-in-users": "As informações de suporte são exclusivas para usuários logados.", - "support-title": "Título de suporte", - "support-content": "Conteúdo de suporte", "accessibility": "Acessibilidade", "accessibility-page-enabled": "Página de acessibilidade habilitada", "accessibility-info-not-added-yet": "As informações de acessibilidade ainda não foram adicionadas", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Trabalho agendado excluído com sucesso", "cron-job-pause-failed": "Falha ao parar trabalho agendado", "cron-job-paused": "Agendamento de trabalho parado com sucesso", - "cron-job-resume-failed": "Falha ao retomar trabalho agendado", - "cron-job-resumed": "Trabalho agendado retomado com sucesso", - "cron-job-start-failed": "Falha ao iniciar trabalho agendado", - "cron-job-started": "Trabalho agendado iniciado com sucesso", - "cron-migration-errors": "Erros na Migração", - "cron-migration-warnings": "Alertas na Migração", - "cron-no-errors": "Nenhum erro a exibir", - "cron-error-severity": "Gravidade", - "cron-error-time": "Tempo", - "cron-error-message": "Mensagem de Erro", - "cron-error-details": "Detalhes", - "cron-clear-errors": "Limpar Todos os Erros", - "cron-retry-failed": "Tentar novamente migrações que falharam", - "cron-resume-paused": "Retomar Migrações Pausadas", - "cron-errors-cleared": "Todos os erros limpos com sucesso", - "cron-no-failed-migrations": "Sem migrações com falha para tentar novamente", - "cron-no-paused-migrations": "Nenhuma migração pausada a ser retomada", - "cron-migrations-resumed": "Migrações retomadas com sucesso", - "cron-migrations-retried": "Migrações com falha reexecutadas com sucesso", - "complete": "Concluído", - "idle": "Parado", "filesystem-path-description": "Caminho base para o armazenamento de arquivos", "gridfs-enabled": "GridFS Habilitado", "gridfs-enabled-description": "Use MongoDB GridFS para armazenamento de arquivos", - "all-migrations": "Todas as Migrações", - "select-migration": "Selecionar Migração", - "start": "Data início", - "pause": "Parar", - "stop": "Parar", - "migration-starting": "Iniciando migrações...", - "migration-pausing": "Pausando migrações...", - "migration-stopping": "Parando migrações...", "migration-pause-failed": "Falha ao parar migrações", "migration-paused": "Migrações paradas com sucesso", "migration-progress": "Progresso das Migrações", "migration-start-failed": "Falha ao iniciar migrações", "migration-started": "Migrações iniciadas com sucesso", - "migration-not-needed": "Nenhuma migração necessária", "migration-status": "Status das Migrações", "migration-stop-confirm": "Você tem certeza que deseja parar todas as migrações?", "migration-stop-failed": "Falha ao parar migrações", @@ -1466,7 +1380,7 @@ "s3-secret-key": "Chave secreta do S3", "s3-secret-key-description": "Chave secreta do AWS S3 para autenticação", "s3-secret-key-placeholder": "Insira a chave secreta do S3", - "s3-secret-key-required": "É necessária uma chave secreta S3", + "s3-secret-key-required": "Chave secreta do S3 é requerida", "s3-settings-save-failed": "Falha ao salvar configurações do S3", "s3-settings-saved": "Configurações do S3 salvas com sucesso", "s3-ssl-enabled": "S3 SSL Habilitado", @@ -1490,73 +1404,7 @@ "back-to-settings": "Voltar às Configurações", "board-id": "ID do Quadro", "board-migration": "Migração de Quadro", - "board-migrations": "Migração de Quadros", "card-show-lists-on-minicard": "Mostrar Listas no Mini cartão", - "comprehensive-board-migration": "Migração de Quadros abrangente", - "comprehensive-board-migration-description": "Realiza verificações e correções abrangentes para a integridade dos dados do quadro, incluindo a ordem da lista, as posições dos cartões e a estrutura das raias.", - "delete-duplicate-empty-lists-migration": "Apagar Listas Vazias Duplicadas", - "delete-duplicate-empty-lists-migration-description": "Exclui com segurança listas duplicadas vazias. Remove apenas listas que não contêm cartões E que possuem outra lista com o mesmo título que contém cartões.", - "lost-cards": "Cartões Perdidos", - "lost-cards-list": "Itens Recuperados", - "restore-lost-cards-migration": "Recuperar Cartões Perdidos", - "restore-lost-cards-migration-description": "Encontra e restaura cartões e listas com ID de raia ou ID de lista ausentes. Cria uma raia \"Cartões Perdidos\" para tornar todos os itens perdidos visíveis novamente.", - "restore-all-archived-migration": "Recuperar Todos Arquivados", - "restore-all-archived-migration-description": "Restaura todas as raias, listas e cartões arquivados. Corrige automaticamente qualquer ID de raia ou ID de lista ausente para tornar os itens visíveis.", - "fix-missing-lists-migration": "Corrigir Listas Ausentes", - "fix-missing-lists-migration-description": "Detecta e repara listas ausentes ou corrompidas na estrutura do quadro.", - "fix-avatar-urls-migration": "Corrigir URLs de Avatar", - "fix-avatar-urls-migration-description": "Atualiza os URLs dos avatares dos membros do quadro para que utilizem o backend de armazenamento correto e corrige referências de avatar quebradas.", - "fix-all-file-urls-migration": "Corrigir todas URLs de arquivos", - "fix-all-file-urls-migration-description": "Atualiza todas URLs de arquivos de anexo neste quadro para usar o backend de armazenamento correto e corrige referências de arquivos quebradas.", - "migration-needed": "Migração necessária", - "migration-complete": "Concluído", - "migration-running": "Executando...", - "migration-successful": "Migração concluída com sucesso.", - "migration-failed": "A migração falhou", - "migrations": "Migrações", - "migrations-admin-only": "Somente os administradores do quadro podem executar migrações.", - "migrations-description": "Execute verificações de integridade de dados e reparos para este quadro. Cada migração pode ser executada individualmente.", - "no-issues-found": "Nenhum problema encontrado", - "run-migration": "Executar Migração", - "run-comprehensive-migration-confirm": "Isso realizará uma migração completa para verificar e corrigir a integridade dos dados do quadro. Isso pode levar alguns instantes. Continuar?", - "run-delete-duplicate-empty-lists-migration-confirm": "Primeiro, isso converterá todas as listas compartilhadas em listas por raia e, em seguida, excluirá as listas vazias que contêm listas duplicadas com o mesmo título e que possuem cartões. Somente as listas vazias realmente redundantes serão removidas. Continuar?", - "run-restore-lost-cards-migration-confirm": "Isso criará uma raia \"Cartões Perdidos\" e restaurará todos os cartões e listas com ID de raia ou ID de lista ausentes. Isso afeta apenas itens não arquivados. Continuar?", - "run-restore-all-archived-migration-confirm": "Isso restaurará TODAS as raias, listas e cartões arquivados, tornando-os visíveis novamente. Quaisquer itens com IDs ausentes serão corrigidos automaticamente. Esta ação não pode ser desfeita facilmente. Continuar?", - "run-fix-missing-lists-migration-confirm": "Isso detectará e corrigirá listas ausentes ou corrompidas na estrutura do quadro. Continuar?", - "run-fix-avatar-urls-migration-confirm": "Isso atualizará as URLs dos avatares dos membros do quadro para usar o backend de armazenamento correto. Continuar?", - "run-fix-all-file-urls-migration-confirm": "Isso atualizará todas URLs de arquivos de anexos neste quadro para usar o servidor de armazenamento correto. Continuar?", - "restore-lost-cards-nothing-to-restore": "Sem raias, listas ou cartões perdidos para restaurar.", - - "migration-progress-title": "Migração do Quadro em Andamento", - "migration-progress-overall": "Progresso Geral", - "migration-progress-current-step": "Etapa Atual", - "migration-progress-status": "Status", - "migration-progress-details": "Detalhes", - "migration-progress-note": "Aguarde enquanto migramos seu quadro para a estrutura mais recente...", - "steps": "etapas", - "view": "Visão", - "has-swimlanes": "Possui Raias", - - "step-analyze-board-structure": "Analisar a Estrutura do Quadro", - "step-fix-orphaned-cards": "Corrigir Cartões Órfãos", - "step-convert-shared-lists": "Converter Listas Compartilhadas", - "step-ensure-per-swimlane-lists": "Garantir listas por raia", - "step-validate-migration": "Validar Migração", - "step-fix-avatar-urls": "Corrigir URLs de Avatar", - "step-fix-attachment-urls": "Corrigir URLs de anexos", - "step-analyze-lists": "Analisar Listas", - "step-create-missing-lists": "Criar Listas Faltantes", - "step-update-cards": "Atualizar Cartões", - "step-finalize": "Finalizar", - "step-delete-duplicate-empty-lists": "Apagar Listas Vazias Duplicadas", - "step-ensure-lost-cards-swimlane": "Garantir a Raia dos Cartões Perdidos", - "step-restore-lists": "Restaurar Listas", - "step-restore-cards": "Restaurar Cartões", - "step-restore-swimlanes": "Restaurar Raias", - "step-fix-missing-ids": "Corrigir IDs ausentes", - "step-scan-users": "Verificando os avatares dos membros do quadro", - "step-scan-files": "Verificando arquivos de anexos do quadro", - "step-fix-file-urls": "Corrigindo URLs de arquivos", "cleanup": "Limpeza", "cleanup-old-jobs": "Limpar Trabalhos Antigos", "completed": "Completado", @@ -1637,7 +1485,6 @@ "schedule": "Agendar", "search-boards-or-operations": "Buscar quadros ou operações", "show-list-on-minicard": "Mostrar Lista no Mini Cartão", - "showChecklistAtMinicard": "Mostrar Lista de Verificação no Mini Cartão", "showing": "Mostrando", "start-test-operation": "Iniciar Teste de Operação", "start-time": "Hora de início", @@ -1650,37 +1497,7 @@ "total-size": "Tamanho Total", "unmigrated-boards": "Quadros não migrados", "weight": "Carga", - "cron": "Cron", - "current-step": "Etapa Atual", - "otp": "Código OTP", - "create-account": "Criar uma conta", - "already-account": "Já tem uma conta? Iniciar sessão", - "available-repositories": "Repositórios disponíveis", - "repositories": "Repositórios", - "repository": "Repositório", - "repository-name": "Nome do repositório", - "size-bytes": "Tamanho (bytes)", - "last-modified": "Última modificação", - "no-repositories": "Nenhum repositório encontrado", - "create-repository": "Criar repositório", - "upload-repository": "Carregar/Atualizar Repositório", - "api-endpoints": "Endpoints da API", - "sign-in-to-upload": "Faça login para enviar repositórios", - "account-locked": "A conta foi bloqueada temporariamente devido a um número excessivo de tentativas de login malsucedidas. Tente novamente mais tarde.", - "otp-required": "Código OTP necessário", - "invalid-credentials": "Nome de usuário ou senha inválidos", - "username-password-required": "É necessário nome de usuário e senha", - "password-mismatch": "As senhas não coincidem", - "username-too-short": "O nome de usuário deve ter pelo menos 3 caracteres", - "user-exists": "O usuário já existe", - "account-created": "Conta criada! Agora você pode entrar.", - "account-creation-failed": "Falha ao criar a conta", - "login": "Conecte-se", - "confirm": "Confirmar", - "error": "Erro", - "file": "Arquivo", - "log": "Log", - "logout": "Sair", - "server": "Servidor", - "protocol": "Protocolo" + "idle": "Parado", + "complete": "Concluído", + "cron": "Cron" } diff --git a/imports/i18n/data/pt.i18n.json b/imports/i18n/data/pt.i18n.json index 777086d2c..1f228c838 100644 --- a/imports/i18n/data/pt.i18n.json +++ b/imports/i18n/data/pt.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "apagou o comentário %s", "activity-receivedDate": "editou a data recebida para %s de %s", "activity-startDate": "editou a data de início para %s de %s", - "allboards.starred": "Starred", - "allboards.templates": "Modelos", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "editou a data limite para %s de %s", "activity-endDate": "editou a data de fim para %s de %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Adicionar Cartão", "add-card-to-top-of-list": "Adicionar Cartão no Topo da Lista", "add-card-to-bottom-of-list": "Adicionar Cartão no Fundo da Lista", - "addListPopup-title": "Adicionar Lista", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Adicionar Membros", "added": "Added", - "addMemberPopup-title": "Adicionar Membros", + "addMemberPopup-title": "Membros", "memberPopup-title": "Configuração dos Membros", "admin": "Administrador", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Pode ver e editar cartões, remover membros e alterar configurações do quadro.", "admin-announcement": "Anúncio", "admin-announcement-active": "Anúncio Activo em Todo o Sistema", "admin-announcement-title": "Anúncio do Administrador", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s estrelas", "board-not-found": "Quadro não encontrado", "board-private-info": "Este quadro será <strong>privado</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Alterar as permissões", "change-settings": "Alterar as Configurações", "changeAvatarPopup-title": "Alterar o Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Alterar o Idioma", "changePasswordPopup-title": "Alterar a Senha", "changePermissionsPopup-title": "Alterar as Permissões", @@ -335,16 +316,10 @@ "comment-placeholder": "Escrever o Comentário", "comment-only": "Apenas comentários", "comment-only-desc": "Pode comentar apenas em cartões.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Eliminar comentário?", "no-comments": "Sem comentários", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Não pode ver comentários nem actividades.", "worker": "Trabalhador", "worker-desc": "Apenas pode mover cartões, atribuir-se a si próprio ao cartão e comentar.", "computer": "Computador", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Eliminar lista de verificação?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copiar a ligação do cartão para a área de transferência", "copy-text-to-clipboard": "Copiar texto para a área de transferência", "linkCardPopup-title": "Ligar Cartão", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Título do primeiro cartão\", \"description\":\"Descrição do primeiro cartão\"}, {\"title\":\"Título do segundo cartão\",\"description\":\"Descrição do segundo cartão\"},{\"title\":\"Título do último cartão\",\"description\":\"Descrição do último cartão\"} ]", "create": "Criar", "createBoardPopup-title": "Criar Quadro", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Importar quadro", "createLabelPopup-title": "Criar Etiqueta", "createCustomField": "Criar Campo", @@ -385,7 +358,7 @@ "date": "Data", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Rejeitar", "default-avatar": "Avatar por omissão", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Pode mover uma lista para o Arquivo para a remover do quadro e preservar a actividade.", "lists": "Listas", "swimlanes": "Pistas", - "calendar": "Calendário", - "gantt": "Gantt", "log-out": "Terminar a Sessão", "log-in": "Entrar", "loginPopup-title": "Entrar", "memberMenuPopup-title": "Configuração dos Membros", - "grey-icons": "Grey Icons", "members": "Membros", "menu": "Menu", "move-selection": "Mover a selecção", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Mover para o Fundo", "moveCardToTop-title": "Mover para o Topo", "moveSelectionPopup-title": "Mover a selecção", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Selecção Múltipla", "multi-selection-label": "Definir etiqueta para seleção", "multi-selection-member": "Defina membro para seleção", @@ -587,8 +555,6 @@ "no-results": "Nenhum resultado.", "normal": "Normal", "normal-desc": "Pode ver e editar cartões. Não pode alterar configurações.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Convite ainda não aceite", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receber actualizações de qualquer quadro, lista ou cartões que estiver a observar", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Permitir Alteração do E-mail", "accounts-allowUserNameChange": "Permitir Alteração de Nome de Utilizador", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Visibilidade do Quadro", + "tableVisibilityMode" : "Visibilidade do Quadro", "createdAt": "Criado em", "modifiedAt": "Modificado em", "verified": "Verificado", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Alterar data de recebimento", "editCardEndDatePopup-title": "Alterar data de fim", "setCardColorPopup-title": "Definir cor", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Escolha uma cor", "setSwimlaneColorPopup-title": "Escolha uma cor", "setListColorPopup-title": "Escolha uma cor", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Todas as listas, cartões, etiquetas e atividades serão apagadas e não poderá recuperar o conteúdo do quadro. Não é reversível.", "boardDeletePopup-title": "Apagar Quadro?", "delete-board": "Apagar Quadro", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Sub-tarefas para o quadro __board__", @@ -942,13 +905,6 @@ "authentication-method": "Método de autenticação", "authentication-type": "Tipo de autenticação", "custom-product-name": "Nome Personalizado do Produto", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Esconder Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modificou a data de fim para", "a-startAt": "modificou a data de início para", "a-receivedAt": "modificou a data recebida para", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "a data limite actual %s está-se a aproximar", "pastdue": "a data limite actual %s já passou", "duenow": "a data limite actual %s é hoje", @@ -989,7 +943,7 @@ "act-almostdue": "estava a lembrar que a data limite actual (__timeValue__) de __card__ está-se a aproximar", "act-pastdue": "estava a lembrar que a data limite (__timeValue__) de __card__ já passou", "act-duenow": "estava a lembrar que a data limite (__timeValue__) de __card__ é agora", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Foi mencionado em [__board__] __list__/__card__", "delete-user-confirm-popup": "Tem a certeza que pretende apagar esta conta? Não é reversível.", "delete-team-confirm-popup": "Tem a certeza que pretende apagar esta equipa? Não é reversível.", "delete-org-confirm-popup": "Tem a certeza que pretende apagar esta organização? Não é reversível.", @@ -1013,7 +967,6 @@ "view-all": "Ver Todos", "filter-by-unread": "Filtrar por não lidos", "mark-all-as-read": "Marcar todos como lidos", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Apagar todos os lidos", "allow-rename": "Permitir Renomear", "allowRenamePopup-title": "Permitir Renomear", @@ -1048,10 +1001,6 @@ "person": "Pessoa", "my-cards": "Meus Cartões", "card": "Cartão", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Lista", "board": "Quadro", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Tempo", - "cron-error-message": "Error Message", - "cron-error-details": "Detalhes", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Concluído", - "idle": "Inativo", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Data de início", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Concluído", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Estado", - "migration-progress-details": "Detalhes", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completada", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Peso", - "cron": "Agendamento", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirmar", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Inativo", + "complete": "Concluído", + "cron": "Agendamento" } diff --git a/imports/i18n/data/pt_PT.i18n.json b/imports/i18n/data/pt_PT.i18n.json index b125d5a72..fa36c3204 100644 --- a/imports/i18n/data/pt_PT.i18n.json +++ b/imports/i18n/data/pt_PT.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "apagou o comentário %s", "activity-receivedDate": "editou a data recebida para %s de %s", "activity-startDate": "editou a data de início para %s de %s", - "allboards.starred": "Starred", - "allboards.templates": "Modelos", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "editou a data limite para %s de %s", "activity-endDate": "editou a data de fim para %s de %s", "add-attachment": "Adicionar Anexo", @@ -98,7 +86,6 @@ "add-card": "Adicionar Cartão", "add-card-to-top-of-list": "Adicionar Cartão no Topo da Lista", "add-card-to-bottom-of-list": "Adicionar Cartão no Fundo da Lista", - "addListPopup-title": "Adicionar Lista", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Adicionar Membros", "added": "Adicionado", - "addMemberPopup-title": "Adicionar Membros", + "addMemberPopup-title": "Membros", "memberPopup-title": "Configuração dos Membros", "admin": "Administrador", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Pode ver e editar cartões, remover membros e alterar configurações do quadro.", "admin-announcement": "Anúncio", "admin-announcement-active": "Anúncio Activo em Todo o Sistema", "admin-announcement-title": "Anúncio do Administrador", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Mostrar na página Todos os Quadros", - "board-info-on-my-boards": "Configurações de todos os quadros", - "boardInfoOnMyBoardsPopup-title": "Configurações de todos os quadros", + "show-at-all-boards-page" : "Mostrar na página Todos os Quadros", + "board-info-on-my-boards" : "Configurações de todos os quadros", + "boardInfoOnMyBoardsPopup-title" : "Configurações de todos os quadros", "boardInfoOnMyBoards-title": "Configurações de todos os quadros", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s estrelas", "board-not-found": "Quadro não encontrado", "board-private-info": "Este quadro será <strong>privado</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Alterar as permissões", "change-settings": "Alterar as Configurações", "changeAvatarPopup-title": "Alterar o Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Alterar o Idioma", "changePasswordPopup-title": "Alterar a Senha", "changePermissionsPopup-title": "Alterar as Permissões", @@ -335,16 +316,10 @@ "comment-placeholder": "Escrever o Comentário", "comment-only": "Apenas comentários", "comment-only-desc": "Pode comentar apenas em cartões.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Eliminar comentário?", "no-comments": "Sem comentários", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Não pode ver comentários nem actividades.", "worker": "Trabalhador", "worker-desc": "Apenas pode mover cartões, atribuir-se a si próprio ao cartão e comentar.", "computer": "Computador", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Eliminar lista de verificação?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copiar a ligação do cartão para a área de transferência", "copy-text-to-clipboard": "Copiar texto para a área de transferência", "linkCardPopup-title": "Ligar Cartão", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Título do primeiro cartão\", \"description\":\"Descrição do primeiro cartão\"}, {\"title\":\"Título do segundo cartão\",\"description\":\"Descrição do segundo cartão\"},{\"title\":\"Título do último cartão\",\"description\":\"Descrição do último cartão\"} ]", "create": "Criar", "createBoardPopup-title": "Criar Quadro", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Importar quadro", "createLabelPopup-title": "Criar Etiqueta", "createCustomField": "Criar Campo", @@ -385,7 +358,7 @@ "date": "Data", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Rejeitar", "default-avatar": "Avatar por omissão", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Pode mover uma lista para o Arquivo para a remover do quadro e preservar a actividade.", "lists": "Listas", "swimlanes": "Pistas", - "calendar": "Calendário", - "gantt": "Gantt", "log-out": "Terminar a Sessão", "log-in": "Entrar", "loginPopup-title": "Entrar", "memberMenuPopup-title": "Configuração dos Membros", - "grey-icons": "Grey Icons", "members": "Membros", "menu": "Menu", "move-selection": "Mover a selecção", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Mover para o Fundo", "moveCardToTop-title": "Mover para o Topo", "moveSelectionPopup-title": "Mover a selecção", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Selecção Múltipla", "multi-selection-label": "Definir etiqueta para seleção", "multi-selection-member": "Defina membro para seleção", @@ -587,8 +555,6 @@ "no-results": "Nenhum resultado.", "normal": "Normal", "normal-desc": "Pode ver e editar cartões. Não pode alterar configurações.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Convite ainda não aceite", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receber actualizações de qualquer quadro, lista ou cartões que estiver a observar", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Permitir Alteração do E-mail", "accounts-allowUserNameChange": "Permitir Alteração de Nome de Utilizador", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Visibilidade do Quadro", + "tableVisibilityMode" : "Visibilidade do Quadro", "createdAt": "Criado em", "modifiedAt": "Modificado em", "verified": "Verificado", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Alterar data de recebimento", "editCardEndDatePopup-title": "Alterar data de fim", "setCardColorPopup-title": "Definir cor", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Escolha uma cor", "setSwimlaneColorPopup-title": "Escolha uma cor", "setListColorPopup-title": "Escolha uma cor", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Todas as listas, cartões, etiquetas e atividades serão apagadas e não poderá recuperar o conteúdo do quadro. Não é reversível.", "boardDeletePopup-title": "Apagar Quadro?", "delete-board": "Apagar Quadro", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Sub-tarefas para o quadro __board__", @@ -942,13 +905,6 @@ "authentication-method": "Método de autenticação", "authentication-type": "Tipo de autenticação", "custom-product-name": "Nome Personalizado do Produto", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Esconder Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modificou a data de fim para", "a-startAt": "modificou a data de início para", "a-receivedAt": "modificou a data recebida para", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "a data limite actual %s está-se a aproximar", "pastdue": "a data limite actual %s já passou", "duenow": "a data limite actual %s é hoje", @@ -989,7 +943,7 @@ "act-almostdue": "estava a lembrar que a data limite actual (__timeValue__) de __card__ está-se a aproximar", "act-pastdue": "estava a lembrar que a data limite (__timeValue__) de __card__ já passou", "act-duenow": "estava a lembrar que a data limite (__timeValue__) de __card__ é agora", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Foi mencionado em [__board__] __list__/__card__", "delete-user-confirm-popup": "Tem a certeza que pretende apagar esta conta? Não é reversível.", "delete-team-confirm-popup": "Tem a certeza que pretende apagar esta equipa? Não é reversível.", "delete-org-confirm-popup": "Tem a certeza que pretende apagar esta organização? Não é reversível.", @@ -1013,7 +967,6 @@ "view-all": "Ver Todos", "filter-by-unread": "Filtrar por não lidos", "mark-all-as-read": "Marcar todos como lidos", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Apagar todos os lidos", "allow-rename": "Permitir Renomear", "allowRenamePopup-title": "Permitir Renomear", @@ -1048,10 +1001,6 @@ "person": "Pessoa", "my-cards": "Meus Cartões", "card": "Cartão", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Lista", "board": "Quadro", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Tempo", - "cron-error-message": "Error Message", - "cron-error-details": "Detalhes", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Data de início", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Estado", - "migration-progress-details": "Detalhes", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completada", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirmar", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ro-RO.i18n.json b/imports/i18n/data/ro-RO.i18n.json index 083e39d59..0adfeaf0e 100644 --- a/imports/i18n/data/ro-RO.i18n.json +++ b/imports/i18n/data/ro-RO.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Adaugă Membrii", "added": "S-a adăugat", - "addMemberPopup-title": "Adaugă Membrii", + "addMemberPopup-title": "Membrii", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Poate vedea și edita carduri, șterge membrii, și schimba setările tablei.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stele", "board-not-found": "Tabla nu a fost găsită", "board-private-info": "Această tabla va fi <strong>privată</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Liste", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Membrii", "menu": "Meniu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ro.i18n.json b/imports/i18n/data/ro.i18n.json index fdd2ce713..5fbd58448 100644 --- a/imports/i18n/data/ro.i18n.json +++ b/imports/i18n/data/ro.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ru-UA.i18n.json b/imports/i18n/data/ru-UA.i18n.json index beda33b50..055c85cdc 100644 --- a/imports/i18n/data/ru-UA.i18n.json +++ b/imports/i18n/data/ru-UA.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Шаблоны", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Добавить простой список", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Добавить участника", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Добавить Шаблон Контейнера", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Календарь", - "gantt": "Диаграмма Ганта", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Время", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "В работе с", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Статус", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Завершен", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ru.i18n.json b/imports/i18n/data/ru.i18n.json index d3d0b8c9c..354f21a23 100644 --- a/imports/i18n/data/ru.i18n.json +++ b/imports/i18n/data/ru.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "удалил комментарий %s", "activity-receivedDate": "отредактировал дату получения на %sс %s", "activity-startDate": "отредактировал дату начала на %sс %s", - "allboards.starred": "Starred", - "allboards.templates": "Шаблоны", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "отредактировал срок исполнения на %s с %s", "activity-endDate": "отредактировал дату завершения на %s с %s", "add-attachment": "Добавить вложение", @@ -98,7 +86,6 @@ "add-card": "Добавить карточку", "add-card-to-top-of-list": "Добавить карточку в начало списка", "add-card-to-bottom-of-list": "Добавить карточку в конец списка", - "addListPopup-title": "Добавить простой список", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Добавить после списка", "add-members": "Добавить участника", "added": "Добавлено", - "addMemberPopup-title": "Добавить участника", + "addMemberPopup-title": "Участники", "memberPopup-title": "Настройки участника", "admin": "Администратор", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Может просматривать и редактировать карточки, удалять участников и управлять настройками доски.", "admin-announcement": "Объявление", "admin-announcement-active": "Действующее общесистемное объявление", "admin-announcement-title": "Объявление от Администратора", @@ -167,16 +154,12 @@ "board-background-image-url": "URL фона", "add-background-image": "Добавить фон", "remove-background-image": "Удалить фон", - "show-at-all-boards-page": "Показать на всех досках", - "board-info-on-my-boards": "Настройки всех досок", - "boardInfoOnMyBoardsPopup-title": "Настройки всех досок", + "show-at-all-boards-page" : "Показать на всех досках", + "board-info-on-my-boards" : "Настройки всех досок", + "boardInfoOnMyBoardsPopup-title" : "Настройки всех досок", "boardInfoOnMyBoards-title": "Настройки всех досок", "show-card-counter-per-list": "Показывать количество карточек в списке", "show-board_members-avatar": "Показать аватары участников доски", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s избранное", "board-not-found": "Доска не найдена", "board-private-info": "Это доска будет <strong>частной</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Изменить права доступа", "change-settings": "Изменить настройки", "changeAvatarPopup-title": "Изменить аватар", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Сменить язык", "changePasswordPopup-title": "Изменить пароль", "changePermissionsPopup-title": "Изменить настройки доступа", @@ -335,16 +316,10 @@ "comment-placeholder": "Написать комментарий", "comment-only": "Только комментирование", "comment-only-desc": "Может комментировать только карточки.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Вы уверены, что хотите удалить этот комментарий?", "deleteCommentPopup-title": "Удалить комментарий?", "no-comments": "Без комментариев", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Не видит комментарии и историю действий.", "worker": "Исполнитель", "worker-desc": "Может перемещать карточки, отмечаться как исполнитель и оставлять комментарии", "computer": "Загрузить с компьютера", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Вы уверены, что хотите удалить чек-лист?", "subtaskDeletePopup-title": "Удалить Подзадачу?", "checklistDeletePopup-title": "Удалить Чек-лист?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Копировать ссылку на карточку в буфер обмена", "copy-text-to-clipboard": "Скопировать текст в буфер обмена", "linkCardPopup-title": "Карточка-ссылка", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Название первой карточки\", \"description\":\"Описание первой карточки\"}, {\"title\":\"Название второй карточки\",\"description\":\"Описание второй карточки\"},{\"title\":\"Название последней карточки\",\"description\":\"Описание последней карточки\"} ]", "create": "Создать", "createBoardPopup-title": "Создать доску", - "createTemplateContainerPopup-title": "Добавить Шаблон Контейнера", "chooseBoardSourcePopup-title": "Импортировать доску", "createLabelPopup-title": "Создать метку", "createCustomField": "Создать поле", @@ -385,7 +358,7 @@ "date": "Дата", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Отклонить", "default-avatar": "Аватар по умолчанию", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Вы можете отправить список в Архив, чтобы убрать его с доски и при этом сохранить результаты.", "lists": "Списки", "swimlanes": "Дорожки", - "calendar": "Календарь", - "gantt": "Диаграмма Ганта", "log-out": "Выйти", "log-in": "Войти", "loginPopup-title": "Войти", "memberMenuPopup-title": "Настройки участника", - "grey-icons": "Grey Icons", "members": "Участники", "menu": "Меню", "move-selection": "Переместить выделение", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Переместить вниз", "moveCardToTop-title": "Переместить вверх", "moveSelectionPopup-title": "Переместить выделение", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Выбрать несколько", "multi-selection-label": "Задать метку для отмеченного", "multi-selection-member": "Задать участника для отмеченного", @@ -587,8 +555,6 @@ "no-results": "Ничего не найдено", "normal": "Обычный", "normal-desc": "Может редактировать карточки. Не может управлять настройками.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Приглашение еще не принято", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Получать обновления по любым доскам, спискам и карточкам, на которые вы подписаны как наблюдатель.", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Разрешить изменение электронной почты", "accounts-allowUserNameChange": "Разрешить изменение имени пользователя", "tableVisibilityMode-allowPrivateOnly": "Видимость досок: Показывать только частные доски", - "tableVisibilityMode": "Видимость досок", + "tableVisibilityMode" : "Видимость досок", "createdAt": "Создан", "modifiedAt": "Изменено", "verified": "Подтвержден", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Изменить дату получения", "editCardEndDatePopup-title": "Изменить дату завершения", "setCardColorPopup-title": "Задать цвет", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Выберите цвет", "setSwimlaneColorPopup-title": "Выберите цвет", "setListColorPopup-title": "Выберите цвет", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Все списки, карточки, метки и действия будут удалены, и вы не сможете восстановить содержимое доски. Отменить нельзя.", "boardDeletePopup-title": "Удалить доску?", "delete-board": "Удалить доску", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Подзадача для доски __board__", @@ -942,13 +905,6 @@ "authentication-method": "Способ авторизации", "authentication-type": "Тип авторизации", "custom-product-name": "Собственное наименование", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Внешний вид", "hide-logo": "Скрыть логотип", "hide-card-counter-list": "Скрыть список счетчиков карточек на всех досках", @@ -979,8 +935,6 @@ "a-endAt": "изменил время завершения на", "a-startAt": "изменил время начала работы на", "a-receivedAt": "изменил время получения на", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "текущий срок выполнения %s приближается", "pastdue": "текущий срок выполнения %s прошел", "duenow": "текущий срок выполнения %s сегодня", @@ -989,7 +943,7 @@ "act-almostdue": "напомнил, что скоро завершается срок выполнения (__timeValue__) карточки __card__", "act-pastdue": "напомнил, что срок выполнения (__timeValue__) карточки __card__ прошел", "act-duenow": "напомнил, что срок выполнения (__timeValue__) карточки __card__ — это уже сейчас", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Вас упомянули в [__board__] __list__/__card__", "delete-user-confirm-popup": "Вы уверены, что хотите удалить аккаунт? Данное действие необратимо.", "delete-team-confirm-popup": "Вы уверены, что хотите удалить эту команду? Эту операцию нельзя отменить.", "delete-org-confirm-popup": "Вы уверены, что хотите удалить эту организацию? Эту операцию нельзя отменить.", @@ -1013,7 +967,6 @@ "view-all": "Показать все", "filter-by-unread": "Фильтр по непрочитанным", "mark-all-as-read": "Отметить все как прочитанные", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Удалить все прочитанные", "allow-rename": "Разрешить переименование", "allowRenamePopup-title": "Разрешить переименование", @@ -1048,10 +1001,6 @@ "person": "Представитель", "my-cards": "Мои карточки", "card": "Карточка", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Список", "board": "Доска", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Время", - "cron-error-message": "Error Message", - "cron-error-details": "Детали", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Завершено", - "idle": "Простой", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "В работе с", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Завершено", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Статус", - "migration-progress-details": "Детали", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Завершен", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Вес", - "cron": "Планировщик", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Подтвердить", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Простой", + "complete": "Завершено", + "cron": "Планировщик" } diff --git a/imports/i18n/data/ru_RU.i18n.json b/imports/i18n/data/ru_RU.i18n.json deleted file mode 100644 index d9ef3e7d3..000000000 --- a/imports/i18n/data/ru_RU.i18n.json +++ /dev/null @@ -1,1686 +0,0 @@ -{ - "accept": "Accept", - "act-activity-notify": "Activity Notification", - "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createBoard": "created board __board__", - "act-createSwimlane": "created swimlane __swimlane__ to board __board__", - "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-createCustomField": "created custom field __customField__ at board __board__", - "act-deleteCustomField": "deleted custom field __customField__ at board __board__", - "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createList": "added list __list__ to board __board__", - "act-addBoardMember": "added member __member__ to board __board__", - "act-archivedBoard": "Board __board__ moved to Archive", - "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", - "act-importBoard": "imported board __board__", - "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", - "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__", - "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-removeBoardMember": "removed member __member__ from board __board__", - "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-withBoardTitle": "__board__", - "act-withCardTitle": "[__board__] __card__", - "actions": "Actions", - "activities": "Activities", - "activity": "Activity", - "activity-added": "added %s to %s", - "activity-archived": "%s moved to Archive", - "activity-attached": "attached %s to %s", - "activity-created": "created %s", - "activity-changedListTitle": "renamed list to %s", - "activity-customfield-created": "created custom field %s", - "activity-excluded": "excluded %s from %s", - "activity-imported": "imported %s into %s from %s", - "activity-imported-board": "imported %s from %s", - "activity-joined": "joined %s", - "activity-moved": "moved %s from %s to %s", - "activity-on": "on %s", - "activity-removed": "removed %s from %s", - "activity-sent": "sent %s to %s", - "activity-unjoined": "unjoined %s", - "activity-subtask-added": "added subtask to %s", - "activity-checked-item": "checked %s in checklist %s of %s", - "activity-unchecked-item": "unchecked %s in checklist %s of %s", - "activity-checklist-added": "added checklist to %s", - "activity-checklist-removed": "removed a checklist from %s", - "activity-checklist-completed": "completed checklist %s of %s", - "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", - "activity-checklist-item-added": "added checklist item to '%s' in %s", - "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", - "add": "Add", - "activity-checked-item-card": "checked %s in checklist %s", - "activity-unchecked-item-card": "unchecked %s in checklist %s", - "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "activity-checklist-uncompleted-card": "uncompleted the checklist %s", - "activity-editComment": "edited comment %s", - "activity-deleteComment": "deleted comment %s", - "activity-receivedDate": "edited received date to %s of %s", - "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", - "activity-dueDate": "edited due date to %s of %s", - "activity-endDate": "edited end date to %s of %s", - "add-attachment": "Add Attachment", - "add-board": "Add Board", - "add-template": "Add Template", - "add-card": "Add Card", - "add-card-to-top-of-list": "Add Card to Top of List", - "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", - "setListWidthPopup-title": "Set Widths", - "set-list-width": "Set Widths", - "set-list-width-value": "Set Min & Max Widths (pixels)", - "list-width-error-message": "List widths must be integers greater than 100", - "keyboard-shortcuts-enabled": "Keyboard shortcuts enabled. Click to disable.", - "keyboard-shortcuts-disabled": "Keyboard shortcuts disabled. Click to enable.", - "setSwimlaneHeightPopup-title": "Set Swimlane Height", - "set-swimlane-height": "Set Swimlane Height", - "set-swimlane-height-value": "Swimlane Height (pixels)", - "swimlane-height-error-message": "Swimlane height must be a positive integer", - "add-swimlane": "Add Swimlane", - "add-subtask": "Add Subtask", - "add-checklist": "Add Checklist", - "add-checklist-item": "Add an item to checklist", - "close-add-checklist-item": "Close add an item to checklist form", - "close-edit-checklist-item": "Close edit an item to checklist form", - "convertChecklistItemToCardPopup-title": "Convert to Card", - "add-cover": "Add cover image to minicard", - "add-label": "Add Label", - "add-list": "Add List", - "add-after-list": "Add After List", - "add-members": "Add Members", - "added": "Added", - "addMemberPopup-title": "Add Members", - "memberPopup-title": "Member Settings", - "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", - "admin-announcement": "Announcement", - "admin-announcement-active": "Active System-Wide Announcement", - "admin-announcement-title": "Announcement from Administrator", - "all-boards": "All Boards", - "and-n-other-card": "And __count__ other card", - "and-n-other-card_plural": "And __count__ other cards", - "apply": "Apply", - "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", - "app-try-reconnect": "Try to reconnect.", - "archive": "Move to Archive", - "archive-all": "Move All to Archive", - "archive-board": "Move Board to Archive", - "archive-board-confirm": "Are you sure you want to archive this board?", - "archive-card": "Move Card to Archive", - "archive-list": "Move List to Archive", - "archive-swimlane": "Move Swimlane to Archive", - "archive-selection": "Move selection to Archive", - "archiveBoardPopup-title": "Move Board to Archive?", - "archived-items": "Archive", - "archived-boards": "Boards in Archive", - "restore-board": "Restore Board", - "no-archived-boards": "No Boards in Archive.", - "archives": "Archive", - "template": "Template", - "templates": "Templates", - "template-container": "Template Container", - "add-template-container": "Add Template Container", - "assign-member": "Assign member", - "attached": "attached", - "attachment": "Attachment", - "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.", - "attachmentDeletePopup-title": "Delete Attachment?", - "attachments": "Attachments", - "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (__size__ max)", - "back": "Back", - "board-change-color": "Change color", - "board-change-background-image": "Change Background Image", - "board-background-image-url": "Background Image URL", - "add-background-image": "Add Background Image", - "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", - "boardInfoOnMyBoards-title": "All Boards Settings", - "show-card-counter-per-list": "Show card count per list", - "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", - "board-nb-stars": "%s stars", - "board-not-found": "Board not found", - "board-private-info": "This board will be <strong>private</strong>.", - "board-public-info": "This board will be <strong>public</strong>.", - "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", - "boardChangeColorPopup-title": "Change Board Background", - "boardChangeBackgroundImagePopup-title": "Change Background Image", - "allBoardsChangeColorPopup-title": "Change color", - "allBoardsChangeBackgroundImagePopup-title": "Change Background Image", - "boardChangeTitlePopup-title": "Rename Board", - "boardChangeVisibilityPopup-title": "Change Visibility", - "boardChangeWatchPopup-title": "Change Watch", - "boardMenuPopup-title": "Board Settings", - "allBoardsMenuPopup-title": "Settings", - "boardChangeViewPopup-title": "Board View", - "boards": "Boards", - "board-view": "Board View", - "desktop-mode": "Desktop Mode", - "mobile-mode": "Mobile Mode", - "mobile-desktop-toggle": "Toggle between Mobile and Desktop Mode", - "zoom-in": "Zoom In", - "zoom-out": "Zoom Out", - "click-to-change-zoom": "Click to change zoom level", - "zoom-level": "Zoom Level", - "enter-zoom-level": "Enter zoom level (50-300%):", - "board-view-cal": "Calendar", - "board-view-swimlanes": "Swimlanes", - "board-view-collapse": "Collapse", - "board-view-gantt": "Gantt", - "board-view-lists": "Lists", - "bucket-example": "Like \"Bucket List\" for example", - "calendar-previous-month-label": "Previous Month", - "calendar-next-month-label": "Next Month", - "cancel": "Cancel", - "card-archived": "This card is moved to Archive.", - "board-archived": "This board is moved to Archive.", - "card-comments-title": "This card has %s comment.", - "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.", - "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", - "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", - "card-archive-pop": "Card will not be visible at this list after archiving card.", - "card-archive-suggest-cancel": "You can later restore card from Archive.", - "card-due": "Due", - "card-due-on": "Due on", - "card-spent": "Spent Time", - "card-edit-attachments": "Edit attachments", - "card-edit-custom-fields": "Edit custom fields", - "card-edit-labels": "Edit labels", - "card-edit-members": "Edit members", - "card-labels-title": "Change the labels for the card.", - "card-members-title": "Add or remove members of the board from the card.", - "card-start": "Start", - "card-start-on": "Starts on", - "cardAttachmentsPopup-title": "Attach From", - "cardCustomField-datePopup-title": "Change date", - "cardCustomFieldsPopup-title": "Edit custom fields", - "cardStartVotingPopup-title": "Start a vote", - "positiveVoteMembersPopup-title": "Proponents", - "negativeVoteMembersPopup-title": "Opponents", - "card-edit-voting": "Edit voting", - "editVoteEndDatePopup-title": "Change vote end date", - "allowNonBoardMembers": "Allow all logged in users", - "vote-question": "Voting question", - "vote-public": "Show who voted what", - "vote-for-it": "for it", - "vote-against": "against", - "deleteVotePopup-title": "Delete vote?", - "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", - "cardStartPlanningPokerPopup-title": "Start a Planning Poker", - "card-edit-planning-poker": "Edit Planning Poker", - "editPokerEndDatePopup-title": "Change Planning Poker vote end date", - "poker-question": "Planning Poker", - "poker-one": "1", - "poker-two": "2", - "poker-three": "3", - "poker-five": "5", - "poker-eight": "8", - "poker-thirteen": "13", - "poker-twenty": "20", - "poker-forty": "40", - "poker-oneHundred": "100", - "poker-unsure": "?", - "poker-finish": "Finish", - "poker-result-votes": "Votes", - "poker-result-who": "Who", - "poker-replay": "Replay", - "set-estimation": "Set Estimation", - "deletePokerPopup-title": "Delete planning poker?", - "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", - "cardDeletePopup-title": "Delete Card?", - "cardArchivePopup-title": "Archive Card?", - "cardDetailsActionsPopup-title": "Card Actions", - "cardLabelsPopup-title": "Labels", - "cardMembersPopup-title": "Members", - "cardMorePopup-title": "More", - "cardTemplatePopup-title": "Create template", - "cards": "Cards", - "cards-count": "Cards", - "cards-count-one": "Card", - "casSignIn": "Sign In with CAS", - "cardType-card": "Card", - "cardType-linkedCard": "Linked Card", - "cardType-linkedBoard": "Linked Board", - "change": "Change", - "change-avatar": "Change Avatar", - "change-password": "Change Password", - "change-permissions": "Change permissions", - "change-settings": "Change Settings", - "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", - "changeLanguagePopup-title": "Change Language", - "changePasswordPopup-title": "Change Password", - "changePermissionsPopup-title": "Change Permissions", - "changeSettingsPopup-title": "Change Settings", - "subtasks": "Subtasks", - "checklists": "Checklists", - "click-to-star": "Click to star this board.", - "click-to-unstar": "Click to unstar this board.", - "click-to-enable-auto-width": "Auto list width disabled. Click to enable.", - "click-to-disable-auto-width": "Auto list width enabled. Click to disable.", - "auto-list-width": "Auto list width", - "clipboard": "Clipboard or drag & drop", - "close": "Close", - "close-board": "Close Board", - "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", - "close-card": "Close Card", - "color-black": "black", - "color-blue": "blue", - "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", - "color-green": "green", - "color-indigo": "indigo", - "color-lime": "lime", - "color-magenta": "magenta", - "color-mistyrose": "mistyrose", - "color-navy": "navy", - "color-orange": "orange", - "color-paleturquoise": "paleturquoise", - "color-peachpuff": "peachpuff", - "color-pink": "pink", - "color-plum": "plum", - "color-purple": "purple", - "color-red": "red", - "color-saddlebrown": "saddlebrown", - "color-silver": "silver", - "color-sky": "sky", - "color-slateblue": "slateblue", - "color-white": "white", - "color-yellow": "yellow", - "unset-color": "Unset", - "comments": "Comments", - "comment": "Comment", - "comment-placeholder": "Write Comment", - "comment-only": "Comment only", - "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", - "comment-delete": "Are you sure you want to delete the comment?", - "deleteCommentPopup-title": "Delete comment?", - "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", - "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", - "computer": "Computer", - "confirm-subtask-delete-popup": "Are you sure you want to delete subtask?", - "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", - "subtaskDeletePopup-title": "Delete Subtask?", - "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", - "copy-card-link-to-clipboard": "Copy card link to clipboard", - "copy-text-to-clipboard": "Copy text to clipboard", - "linkCardPopup-title": "Link Card", - "searchElementPopup-title": "Search", - "copyCardPopup-title": "Copy Card", - "copyManyCardsPopup-title": "Copy Template to Many Cards", - "copyManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", - "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", - "create": "Create", - "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", - "chooseBoardSourcePopup-title": "Import board", - "createLabelPopup-title": "Create Label", - "createCustomField": "Create Field", - "createCustomFieldPopup-title": "Create Field", - "current": "current", - "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", - "custom-field-checkbox": "Checkbox", - "custom-field-currency": "Currency", - "custom-field-currency-option": "Currency Code", - "custom-field-date": "Date", - "custom-field-dropdown": "Dropdown List", - "custom-field-dropdown-none": "(none)", - "custom-field-dropdown-options": "List Options", - "custom-field-dropdown-options-placeholder": "Press enter to add more options", - "custom-field-dropdown-unknown": "(unknown)", - "custom-field-number": "Number", - "custom-field-text": "Text", - "custom-fields": "Custom Fields", - "date": "Date", - "date-format": "Date Format", - "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", - "date-format-mm-dd-yyyy": "MM-DD-YYYY", - "decline": "Decline", - "default-avatar": "Default avatar", - "delete": "Delete", - "deleteCustomFieldPopup-title": "Delete Custom Field?", - "deleteLabelPopup-title": "Delete Label?", - "description": "Description", - "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", - "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", - "discard": "Discard", - "done": "Done", - "download": "Download", - "edit": "Edit", - "edit-avatar": "Change Avatar", - "edit-profile": "Edit Profile", - "edit-wip-limit": "Edit WIP Limit", - "soft-wip-limit": "Soft WIP Limit", - "editCardStartDatePopup-title": "Change start date", - "editCardDueDatePopup-title": "Change due date", - "editCustomFieldPopup-title": "Edit Field", - "addReactionPopup-title": "Add reaction", - "editCardSpentTimePopup-title": "Change spent time", - "editLabelPopup-title": "Change Label", - "editNotificationPopup-title": "Edit Notification", - "editProfilePopup-title": "Edit Profile", - "email": "Email", - "email-address": "Email Address", - "email-enrollAccount-subject": "An account created for you on __siteName__", - "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", - "email-fail": "Sending email failed", - "email-fail-text": "Error trying to send email", - "email-invalid": "Invalid email", - "email-invite": "Invite via Email", - "email-invite-subject": "__inviter__ sent you an invitation", - "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", - "email-resetPassword-subject": "Reset your password on __siteName__", - "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", - "email-sent": "Email sent", - "email-verifyEmail-subject": "Verify your email address on __siteName__", - "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", - "enable-vertical-scrollbars": "Enable vertical scrollbars", - "enable-wip-limit": "Enable WIP Limit", - "error-board-doesNotExist": "This board does not exist", - "error-board-notAdmin": "You need to be admin of this board to do that", - "error-board-notAMember": "You need to be a member of this board to do that", - "error-json-malformed": "Your text is not valid JSON", - "error-json-schema": "Your JSON data does not include the proper information in the correct format", - "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format ", - "error-list-doesNotExist": "This list does not exist", - "error-user-doesNotExist": "This user does not exist", - "error-user-notAllowSelf": "You can not invite yourself", - "error-user-notCreated": "This user is not created", - "error-username-taken": "This username is already taken", - "error-orgname-taken": "This organization name is already taken", - "error-teamname-taken": "This team name is already taken", - "error-email-taken": "Email has already been taken", - "export-board": "Export board", - "export-board-json": "Export board to JSON", - "export-board-csv": "Export board to CSV", - "export-board-tsv": "Export board to TSV", - "export-board-excel": "Export board to Excel", - "user-can-not-export-excel": "User can not export Excel", - "export-board-html": "Export board to HTML", - "export-card": "Export card", - "export-card-pdf": "Export card to PDF", - "user-can-not-export-card-to-pdf": "User can not export card to PDF", - "exportBoardPopup-title": "Export board", - "exportCardPopup-title": "Export card", - "sort": "Sort", - "sorted": "Sorted", - "remove-sort": "Remove sort", - "sort-desc": "Click to Sort List", - "list-sort-by": "Sort the List By:", - "list-label-modifiedAt": "Last Access Time", - "list-label-title": "Name of the List", - "list-label-sort": "Your Manual Order", - "list-label-short-modifiedAt": "(L)", - "list-label-short-title": "(N)", - "list-label-short-sort": "(M)", - "filter": "Filter", - "filter-cards": "Filter Cards or Lists", - "filter-dates-label": "Filter by date", - "filter-no-due-date": "No due date", - "filter-overdue": "Overdue", - "filter-due-today": "Due today", - "filter-due-this-week": "Due this week", - "filter-due-next-week": "Due next week", - "filter-due-tomorrow": "Due tomorrow", - "list-filter-label": "Filter List by Title", - "filter-clear": "Clear filter", - "filter-labels-label": "Filter by label", - "filter-no-label": "No label", - "filter-member-label": "Filter by member", - "filter-no-member": "No member", - "filter-assignee-label": "Filter by assignee", - "filter-no-assignee": "No assignee", - "filter-custom-fields-label": "Filter by Custom Fields", - "filter-no-custom-fields": "No Custom Fields", - "filter-show-archive": "Show archived lists", - "filter-hide-empty": "Hide empty lists", - "filter-on": "Filter is on", - "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", - "filter-to-selection": "Filter to selection", - "other-filters-label": "Other Filters", - "advanced-filter-label": "Advanced Filter", - "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", - "fullname": "Full Name", - "header-logo-title": "Go back to your boards page.", - "show-activities": "Show Activities", - "headerBarCreateBoardPopup-title": "Create Board", - "home": "Home", - "import": "Import", - "impersonate-user": "Impersonate user", - "link": "Link", - "import-board": "import board", - "import-board-c": "Import board", - "import-board-title-trello": "Import board from Trello", - "import-board-title-wekan": "Import board from previous export", - "import-board-title-csv": "Import board from CSV/TSV", - "from-trello": "From Trello", - "from-wekan": "From previous export", - "from-csv": "From CSV/TSV", - "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", - "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", - "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", - "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", - "import-json-placeholder": "Paste your valid JSON data here", - "import-csv-placeholder": "Paste your valid CSV/TSV data here", - "import-map-members": "Map members", - "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", - "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", - "import-show-user-mapping": "Review members mapping", - "import-user-select": "Pick your existing user you want to use as this member", - "importMapMembersAddPopup-title": "Select member", - "info": "Version", - "initials": "Initials", - "invalid-date": "Invalid date", - "invalid-time": "Invalid time", - "invalid-user": "Invalid user", - "joined": "joined", - "just-invited": "You are just invited to this board", - "keyboard-shortcuts": "Keyboard shortcuts", - "label-create": "Create Label", - "label-default": "%s label (default)", - "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", - "labels": "Labels", - "language": "Language", - "last-admin-desc": "You can’t change roles because there must be at least one admin.", - "leave-board": "Leave Board", - "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", - "leaveBoardPopup-title": "Leave Board ?", - "link-card": "Link to this card", - "list-archive-cards": "Move all cards in this list to Archive", - "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", - "list-move-cards": "Move all cards in this list", - "list-select-cards": "Select all cards in this list", - "set-color-list": "Set Color", - "listActionPopup-title": "List Actions", - "settingsUserPopup-title": "User Settings", - "settingsTeamPopup-title": "Team Settings", - "settingsOrgPopup-title": "Organization Settings", - "swimlaneActionPopup-title": "Swimlane Actions", - "swimlaneAddPopup-title": "Add a Swimlane below", - "listImportCardPopup-title": "Import a Trello card", - "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", - "listMorePopup-title": "More", - "link-list": "Link to this list", - "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", - "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", - "lists": "Lists", - "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", - "log-out": "Log Out", - "log-in": "Log In", - "loginPopup-title": "Log In", - "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", - "members": "Members", - "menu": "Menu", - "move-selection": "Move selection", - "moveCardPopup-title": "Move Card", - "moveCardToBottom-title": "Move to Bottom", - "moveCardToTop-title": "Move to Top", - "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", - "multi-selection": "Multi-Selection", - "multi-selection-label": "Set label for selection", - "multi-selection-member": "Set member for selection", - "multi-selection-on": "Multi-Selection is on", - "muted": "Muted", - "muted-info": "You will never be notified of any changes in this board", - "my-boards": "My Boards", - "name": "Name", - "no-archived-cards": "No cards in Archive.", - "no-archived-lists": "No lists in Archive.", - "no-archived-swimlanes": "No swimlanes in Archive.", - "no-results": "No results", - "normal": "Normal", - "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", - "not-accepted-yet": "Invitation not accepted yet", - "notify-participate": "Receive updates to any cards you participate as creator or member", - "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", - "optional": "optional", - "or": "or", - "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", - "page-not-found": "Page not found.", - "password": "Password", - "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", - "participating": "Participating", - "preview": "Preview", - "previewAttachedImagePopup-title": "Preview", - "previewClipboardImagePopup-title": "Preview", - "private": "Private", - "private-desc": "This board is private. Only people added to the board can view and edit it.", - "profile": "Profile", - "public": "Public", - "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", - "quick-access-description": "Star a board to add a shortcut in this bar.", - "remove-cover": "Remove cover image from minicard", - "remove-from-board": "Remove from Board", - "remove-label": "Remove Label", - "listDeletePopup-title": "Delete List ?", - "remove-member": "Remove Member", - "remove-member-from-card": "Remove from Card", - "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", - "removeMemberPopup-title": "Remove Member?", - "rename": "Rename", - "rename-board": "Rename Board", - "restore": "Restore", - "rescue-card-description": "Show rescue dialogue before closing for unsaved card descriptions", - "rescue-card-description-dialogue": "Overwrite current card description with your changes?", - "save": "Save", - "search": "Search", - "rules": "Rules", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Write text you search and press Enter", - "select-color": "Select Color", - "select-board": "Select Board", - "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", - "setWipLimitPopup-title": "Set WIP Limit", - "shortcut-add-self": "Add yourself to current card", - "shortcut-assign-self": "Assign yourself to current card", - "shortcut-autocomplete-emoji": "Autocomplete emoji", - "shortcut-autocomplete-members": "Autocomplete members", - "shortcut-clear-filters": "Clear all filters", - "shortcut-close-dialog": "Close Dialog", - "shortcut-filter-my-cards": "Filter my cards", - "shortcut-filter-my-assigned-cards": "Filter my assigned cards", - "shortcut-show-shortcuts": "Bring up this shortcuts list", - "shortcut-toggle-filterbar": "Toggle Filter Sidebar", - "shortcut-toggle-searchbar": "Toggle Search Sidebar", - "shortcut-toggle-sidebar": "Toggle Board Sidebar", - "show-cards-minimum-count": "Show cards count if list contains more than", - "sidebar-open": "Open Sidebar", - "sidebar-close": "Close Sidebar", - "signupPopup-title": "Create an Account", - "star-board-title": "Click to star this board. It will show up at top of your boards list.", - "starred-boards": "Starred Boards", - "starred-boards-description": "Starred boards show up at the top of your boards list.", - "subscribe": "Subscribe", - "team": "Team", - "this-board": "this board", - "this-card": "this card", - "spent-time-hours": "Spent time (hours)", - "overtime-hours": "Overtime (hours)", - "overtime": "Overtime", - "has-overtime-cards": "Has overtime cards", - "has-spenttime-cards": "Has spent time cards", - "time": "Time", - "title": "Title", - "toggle-assignees": "Toggle assignees 1-9 for card (By order of addition to board).", - "toggle-labels": "Toggle labels 1-9 for card. Multi-Selection adds labels 1-9", - "remove-labels-multiselect": "Multi-Selection removes labels 1-9", - "tracking": "Tracking", - "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", - "type": "Type", - "unassign-member": "Unassign member", - "unsaved-description": "You have an unsaved description.", - "unwatch": "Unwatch", - "upload": "Upload", - "upload-avatar": "Upload an avatar", - "uploaded-avatar": "Uploaded an avatar", - "uploading-files": "Uploading files", - "upload-failed": "Upload failed", - "upload-completed": "Upload completed", - "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", - "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", - "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", - "custom-login-logo-image-url": "Custom Login Logo Image URL", - "custom-login-logo-link-url": "Custom Login Logo Link URL", - "custom-help-link-url": "Custom Help Link URL", - "text-below-custom-login-logo": "Text below Custom Login Logo", - "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", - "username": "Username", - "import-usernames": "Import Usernames", - "view-it": "View it", - "warn-list-archived": "warning: this card is in an list at Archive", - "watch": "Watch", - "watching": "Watching", - "watching-info": "You will be notified of any change in this board", - "welcome-board": "Welcome Board", - "welcome-swimlane": "Milestone 1", - "welcome-list1": "Basics", - "welcome-list2": "Advanced", - "card-templates-swimlane": "Card Templates", - "list-templates-swimlane": "List Templates", - "board-templates-swimlane": "Board Templates", - "what-to-do": "What do you want to do?", - "wipLimitErrorPopup-title": "Invalid WIP Limit", - "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", - "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", - "admin-panel": "Admin Panel", - "settings": "Settings", - "people": "People", - "registration": "Registration", - "disable-self-registration": "Disable Self-Registration", - "disable-forgot-password": "Disable Forgot Password", - "invite": "Invite", - "invite-people": "Invite People", - "to-boards": "To board(s)", - "email-addresses": "Email Addresses", - "smtp-host-description": "The address of the SMTP server that handles your emails.", - "smtp-port-description": "The port your SMTP server uses for outgoing emails.", - "smtp-tls-description": "Enable TLS support for SMTP server", - "smtp-host": "SMTP Host", - "smtp-port": "SMTP Port", - "smtp-username": "Username", - "smtp-password": "Password", - "smtp-tls": "TLS support", - "send-from": "From", - "send-smtp-test": "Send a test email to yourself", - "invitation-code": "Invitation Code", - "email-invite-register-subject": "__inviter__ sent you an invitation", - "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", - "email-smtp-test-subject": "SMTP Test Email", - "email-smtp-test-text": "You have successfully sent an email", - "error-invitation-code-not-exist": "Invitation code doesn't exist", - "error-notAuthorized": "You are not authorized to view this page.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional for Authentication)", - "outgoing-webhooks": "Outgoing Webhooks", - "bidirectional-webhooks": "Two-Way Webhooks", - "outgoingWebhooksPopup-title": "Outgoing Webhooks", - "boardCardTitlePopup-title": "Card Title Filter", - "disable-webhook": "Disable This Webhook", - "global-webhook": "Global Webhooks", - "new-outgoing-webhook": "New Outgoing Webhook", - "no-name": "(Unknown)", - "Node_version": "Node version", - "Meteor_version": "Meteor version", - "MongoDB_version": "MongoDB version", - "MongoDB_storage_engine": "MongoDB storage engine", - "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", - "OS_Arch": "OS Arch", - "OS_Cpus": "OS CPU Count", - "OS_Freemem": "OS Free Memory", - "OS_Loadavg": "OS Load Average", - "OS_Platform": "OS Platform", - "OS_Release": "OS Release", - "OS_Totalmem": "OS Total Memory", - "OS_Type": "OS Type", - "OS_Uptime": "OS Uptime", - "days": "days", - "hours": "hours", - "minutes": "minutes", - "seconds": "seconds", - "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Add field to new cards", - "always-field-on-card": "Add field to all cards", - "showLabel-field-on-card": "Show field label on minicard", - "showSum-field-on-list": "Show sum of fields at top of list", - "yes": "Yes", - "no": "No", - "accounts": "Accounts", - "accounts-allowEmailChange": "Allow Email Change", - "accounts-allowUserNameChange": "Allow Username Change", - "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", - "createdAt": "Created at", - "modifiedAt": "Modified at", - "verified": "Verified", - "active": "Active", - "card-received": "Received", - "card-received-on": "Received on", - "card-end": "End", - "card-end-on": "Ends on", - "editCardReceivedDatePopup-title": "Change received date", - "editCardEndDatePopup-title": "Change end date", - "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", - "assigned-by": "Assigned By", - "requested-by": "Requested By", - "card-sorting-by-number": "Card sorting by number", - "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", - "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", - "boardDeletePopup-title": "Delete Board?", - "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", - "delete-duplicate-lists": "Delete Duplicate Lists", - "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", - "default-subtasks-board": "Subtasks for __board__ board", - "default": "Default", - "defaultdefault": "Default", - "queue": "Queue", - "subtask-settings": "Subtasks Settings", - "card-settings": "Card Settings", - "minicard-settings": "Minicard Settings", - "boardSubtaskSettingsPopup-title": "Subtasks Settings", - "boardCardSettingsPopup-title": "Card Settings", - "boardMinicardSettingsPopup-title": "Minicard Settings", - "deposit-subtasks-board": "Deposit subtasks to this board:", - "deposit-subtasks-list": "Landing list for subtasks deposited here:", - "show-parent-in-minicard": "Show parent in minicard:", - "description-on-minicard": "Description on minicard", - "cover-attachment-on-minicard": "Cover image on minicard", - "badge-attachment-on-minicard": "Count of attachments on minicard", - "card-sorting-by-number-on-minicard": "Card sorting by number on minicard", - "prefix-with-full-path": "Prefix with full path", - "prefix-with-parent": "Prefix with parent", - "subtext-with-full-path": "Subtext with full path", - "subtext-with-parent": "Subtext with parent", - "change-card-parent": "Change card's parent", - "parent-card": "Parent card", - "source-board": "Source board", - "no-parent": "Don't show parent", - "activity-added-label": "added label '%s' to %s", - "activity-removed-label": "removed label '%s' from %s", - "activity-delete-attach": "deleted an attachment from %s", - "activity-added-label-card": "added label '%s'", - "activity-removed-label-card": "removed label '%s'", - "activity-delete-attach-card": "deleted an attachment", - "activity-set-customfield": "set custom field '%s' to '%s' in %s", - "activity-unset-customfield": "unset custom field '%s' in %s", - "r-rule": "Rule", - "r-add-trigger": "Add trigger", - "r-add-action": "Add action", - "r-board-rules": "Board rules", - "r-add-rule": "Add rule", - "r-view-rule": "View rule", - "r-delete-rule": "Delete rule", - "r-new-rule-name": "New rule title", - "r-no-rules": "No rules", - "r-trigger": "Trigger", - "r-action": "Action", - "r-when-a-card": "When a card", - "r-is": "is", - "r-is-moved": "is moved", - "r-added-to": "Added to", - "r-removed-from": "Removed from", - "r-the-board": "the board", - "r-list": "list", - "set-filter": "Set Filter", - "r-moved-to": "Moved to", - "r-moved-from": "Moved from", - "r-archived": "Moved to Archive", - "r-unarchived": "Restored from Archive", - "r-a-card": "a card", - "r-when-a-label-is": "When a label is", - "r-when-the-label": "When the label", - "r-list-name": "list name", - "r-when-a-member": "When a member is", - "r-when-the-member": "When the member", - "r-name": "name", - "r-when-a-attach": "When an attachment", - "r-when-a-checklist": "When a checklist is", - "r-when-the-checklist": "When the checklist", - "r-completed": "Completed", - "r-made-incomplete": "Made incomplete", - "r-when-a-item": "When a checklist item is", - "r-when-the-item": "When the checklist item", - "r-checked": "Checked", - "r-unchecked": "Unchecked", - "r-move-card-to": "Move card to", - "r-top-of": "Top of", - "r-bottom-of": "Bottom of", - "r-its-list": "its list", - "r-archive": "Move to Archive", - "r-unarchive": "Restore from Archive", - "r-card": "card", - "r-add": "Add", - "r-remove": "Remove", - "r-label": "label", - "r-member": "member", - "r-remove-all": "Remove all members from the card", - "r-set-color": "Set color to", - "r-checklist": "checklist", - "r-check-all": "Check all", - "r-uncheck-all": "Uncheck all", - "r-items-check": "items of checklist", - "r-check": "Check", - "r-uncheck": "Uncheck", - "r-item": "item", - "r-of-checklist": "of checklist", - "r-send-email": "Send an email", - "r-to": "to", - "r-of": "of", - "r-subject": "subject", - "r-rule-details": "Rule details", - "r-d-move-to-top-gen": "Move card to top of its list", - "r-d-move-to-top-spec": "Move card to top of list", - "r-d-move-to-bottom-gen": "Move card to bottom of its list", - "r-d-move-to-bottom-spec": "Move card to bottom of list", - "r-d-send-email": "Send email", - "r-d-send-email-to": "to", - "r-d-send-email-subject": "subject", - "r-d-send-email-message": "message", - "r-d-archive": "Move card to Archive", - "r-d-unarchive": "Restore card from Archive", - "r-d-add-label": "Add label", - "r-d-remove-label": "Remove label", - "r-create-card": "Create new card", - "r-in-list": "in list", - "r-in-swimlane": "in swimlane", - "r-d-add-member": "Add member", - "r-d-remove-member": "Remove member", - "r-d-remove-all-member": "Remove all member", - "r-d-check-all": "Check all items of a list", - "r-d-uncheck-all": "Uncheck all items of a list", - "r-d-check-one": "Check item", - "r-d-uncheck-one": "Uncheck item", - "r-d-check-of-list": "of checklist", - "r-d-add-checklist": "Add checklist", - "r-d-remove-checklist": "Remove checklist", - "r-by": "by", - "r-add-checklist": "Add checklist", - "r-with-items": "with items", - "r-items-list": "item1,item2,item3", - "r-add-swimlane": "Add swimlane", - "r-swimlane-name": "swimlane name", - "r-board-note": "Note: leave a field empty to match every possible value. ", - "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", - "r-when-a-card-is-moved": "When a card is moved to another list", - "r-set": "Set", - "r-update": "Update", - "r-datefield": "date field", - "r-df-start-at": "start", - "r-df-due-at": "due", - "r-df-end-at": "end", - "r-df-received-at": "received", - "r-to-current-datetime": "to current date/time", - "r-remove-value-from": "Remove value from", - "r-link-card": "Link card to", - "ldap": "LDAP", - "oauth2": "OAuth2", - "cas": "CAS", - "authentication-method": "Authentication method", - "authentication-type": "Authentication type", - "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", - "layout": "Layout", - "hide-logo": "Hide Logo", - "hide-card-counter-list": "Hide card counter list on All Boards", - "hide-board-member-list": "Hide board member list on All Boards", - "add-custom-html-after-body-start": "Add Custom HTML after <body> start", - "add-custom-html-before-body-end": "Add Custom HTML before </body> end", - "error-undefined": "Something went wrong", - "error-ldap-login": "An error occurred while trying to login", - "display-authentication-method": "Display Authentication Method", - "oidc-button-text": "Customize the OIDC button text", - "default-authentication-method": "Default Authentication Method", - "duplicate-board": "Duplicate Board", - "duplicate-board-confirm": "Are you sure you want to duplicate this board?", - "org-number": "The number of organizations is: ", - "team-number": "The number of teams is: ", - "people-number": "The number of people is: ", - "swimlaneDeletePopup-title": "Delete Swimlane ?", - "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", - "restore-all": "Restore all", - "delete-all": "Delete all", - "loading": "Loading, please wait.", - "previous_as": "last time was", - "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", - "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", - "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", - "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", - "a-dueAt": "modified due time to be", - "a-endAt": "modified ending time to be", - "a-startAt": "modified starting time to be", - "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", - "almostdue": "current due time %s is approaching", - "pastdue": "current due time %s is past", - "duenow": "current due time %s is today", - "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", - "act-withDue": "__list__/__card__ due reminders [__board__]", - "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", - "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", - "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", - "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", - "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", - "accounts-allowUserDelete": "Allow users to self delete their account", - "hide-minicard-label-text": "Hide minicard label text", - "show-desktop-drag-handles": "Show desktop drag handles", - "assignee": "Assignee", - "cardAssigneesPopup-title": "Assignee", - "addmore-detail": "Add a more detailed description", - "show-on-card": "Show on Card", - "show-on-minicard": "Show on Minicard", - "new": "New", - "editOrgPopup-title": "Edit Organization", - "newOrgPopup-title": "New Organization", - "editTeamPopup-title": "Edit Team", - "newTeamPopup-title": "New Team", - "editUserPopup-title": "Edit User", - "newUserPopup-title": "New User", - "notifications": "Notifications", - "help": "Help", - "view-all": "View All", - "filter-by-unread": "Filter by Unread", - "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", - "remove-all-read": "Remove all read", - "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename", - "start-day-of-week": "Set day of the week start", - "monday": "Monday", - "tuesday": "Tuesday", - "wednesday": "Wednesday", - "thursday": "Thursday", - "friday": "Friday", - "saturday": "Saturday", - "sunday": "Sunday", - "status": "Status", - "swimlane": "Swimlane", - "owner": "Owner", - "last-modified-at": "Last modified at", - "last-activity": "Last activity", - "voting": "Voting", - "archived": "Archived", - "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", - "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", - "hide-checked-items": "Hide checked items", - "hide-finished-checklist": "Hide finished checklist", - "task": "Task", - "create-task": "Create Task", - "ok": "OK", - "organizations": "Organizations", - "teams": "Teams", - "displayName": "Display Name", - "shortName": "Short Name", - "autoAddUsersWithDomainName": "Automatically add users with the domain name", - "website": "Website", - "person": "Person", - "my-cards": "My Cards", - "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", - "list": "List", - "board": "Board", - "context-separator": "/", - "myCardsViewChange-title": "My Cards View", - "myCardsViewChangePopup-title": "My Cards View", - "myCardsViewChange-choice-boards": "Boards", - "myCardsViewChange-choice-table": "Table", - "myCardsSortChange-title": "My Cards Sort", - "myCardsSortChangePopup-title": "My Cards Sort", - "myCardsSortChange-choice-board": "By Board", - "myCardsSortChange-choice-dueat": "By Due Date", - "dueCards-title": "Due Cards", - "dueCardsViewChange-title": "Due Cards View", - "dueCardsViewChangePopup-title": "Due Cards View", - "dueCardsViewChange-choice-me": "Me", - "dueCardsViewChange-choice-all": "All Users", - "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", - "dueCards-noResults-title": "No Due Cards Found", - "dueCards-noResults-description": "You don't have any cards with due dates at the moment.", - "broken-cards": "Broken Cards", - "board-title-not-found": "Board '%s' not found.", - "swimlane-title-not-found": "Swimlane '%s' not found.", - "list-title-not-found": "List '%s' not found.", - "label-not-found": "Label '%s' not found.", - "label-color-not-found": "Label color %s not found.", - "user-username-not-found": "Username '%s' not found.", - "comment-not-found": "Card with comment containing text '%s' not found.", - "org-name-not-found": "Organization '%s' not found.", - "team-name-not-found": "Team '%s' not found.", - "globalSearch-title": "Search All Boards", - "no-cards-found": "No Cards Found", - "one-card-found": "One Card Found", - "n-cards-found": "%s Cards Found", - "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", - "operator-board": "board", - "operator-board-abbrev": "b", - "operator-swimlane": "swimlane", - "operator-swimlane-abbrev": "s", - "operator-list": "list", - "operator-list-abbrev": "l", - "operator-label": "label", - "operator-label-abbrev": "#", - "operator-user": "user", - "operator-user-abbrev": "@", - "operator-member": "member", - "operator-member-abbrev": "m", - "operator-assignee": "assignee", - "operator-assignee-abbrev": "a", - "operator-creator": "creator", - "operator-status": "status", - "operator-due": "due", - "operator-created": "created", - "operator-modified": "modified", - "operator-sort": "sort", - "operator-comment": "comment", - "operator-has": "has", - "operator-limit": "limit", - "operator-debug": "debug", - "operator-org": "org", - "operator-team": "team", - "predicate-archived": "archived", - "predicate-open": "open", - "predicate-ended": "ended", - "predicate-all": "all", - "predicate-overdue": "overdue", - "predicate-week": "week", - "predicate-month": "month", - "predicate-quarter": "quarter", - "predicate-year": "year", - "predicate-due": "due", - "predicate-modified": "modified", - "predicate-created": "created", - "predicate-attachment": "attachment", - "predicate-description": "description", - "predicate-checklist": "checklist", - "predicate-start": "start", - "predicate-end": "end", - "predicate-assignee": "assignee", - "predicate-member": "member", - "predicate-public": "public", - "predicate-private": "private", - "predicate-selector": "selector", - "predicate-projection": "projection", - "operator-unknown-error": "%s is not an operator", - "operator-number-expected": "operator __operator__ expected a number, got '__value__'", - "operator-sort-invalid": "sort of '%s' is invalid", - "operator-status-invalid": "'%s' is not a valid status", - "operator-has-invalid": "%s is not a valid existence check", - "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", - "operator-debug-invalid": "%s is not a valid debug predicate", - "next-page": "Next Page", - "previous-page": "Previous Page", - "heading-notes": "Notes", - "globalSearch-instructions-heading": "Search Instructions", - "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", - "globalSearch-instructions-operators": "Available operators:", - "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", - "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", - "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", - "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", - "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", - "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", - "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", - "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", - "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", - "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", - "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", - "globalSearch-instructions-operator-org": "`__operator_org__:<display name|short name>` - cards belonging to a board assigned to organization *<name>*", - "globalSearch-instructions-operator-team": "`__operator_team__:<display name|short name>` - cards belonging to a board assigned to team *<name>*", - "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", - "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", - "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", - "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", - "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", - "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", - "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", - "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", - "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", - "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", - "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", - "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", - "globalSearch-instructions-notes-1": "Multiple operators may be specified.", - "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", - "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", - "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", - "globalSearch-instructions-notes-4": "Text searches are case insensitive.", - "globalSearch-instructions-notes-5": "By default archived cards are not searched.", - "link-to-search": "Link to this search", - "excel-font": "Arial", - "number": "Number", - "label-colors": "Label Colors", - "label-names": "Label Names", - "archived-at": "archived at", - "sort-cards": "Sort Cards", - "sort-is-on": "Sort is on", - "cardsSortPopup-title": "Sort Cards", - "due-date": "Due Date", - "server-error": "Server Error", - "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", - "title-alphabetically": "Title (Alphabetically)", - "created-at-newest-first": "Created At (Newest First)", - "created-at-oldest-first": "Created At (Oldest First)", - "links-heading": "Links", - "hide-activities-of-all-boards": "Don't show the board activities on all boards", - "now-activities-of-all-boards-are-hidden": "Now all activities of all boards are hidden", - "move-swimlane": "Move Swimlane", - "moveSwimlanePopup-title": "Move Swimlane", - "custom-field-stringtemplate": "String Template", - "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", - "custom-field-stringtemplate-separator": "Separator (use or   for a space)", - "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", - "creator": "Creator", - "creator-on-minicard": "Creator on minicard", - "filesReportTitle": "Files Report", - "reports": "Reports", - "rulesReportTitle": "Rules Report", - "boardsReportTitle": "Boards Report", - "cardsReportTitle": "Cards Report", - "copy-swimlane": "Copy Swimlane", - "copySwimlanePopup-title": "Copy Swimlane", - "display-card-creator": "Display Card Creator", - "wait-spinner": "Wait Spinner", - "Bounce": "Bounce Wait Spinner", - "Cube": "Cube Wait Spinner", - "Cube-Grid": "Cube-Grid Wait Spinner", - "Dot": "Dot Wait Spinner", - "Double-Bounce": "Double Bounce Wait Spinner", - "Rotateplane": "Rotateplane Wait Spinner", - "Scaleout": "Scaleout Wait Spinner", - "Wave": "Wave Wait Spinner", - "maximize-card": "Maximize Card", - "minimize-card": "Minimize Card", - "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", - "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it", - "subject": "Subject", - "details": "Details", - "carbon-copy": "Carbon Copy (Cc:)", - "ticket": "Ticket", - "tickets": "Tickets", - "ticket-number": "Ticket Number", - "open": "Open", - "pending": "Pending", - "closed": "Closed", - "resolved": "Resolved", - "cancelled": "Cancelled", - "history": "History", - "request": "Request", - "requests": "Requests", - "help-request": "Help Request", - "editCardSortOrderPopup-title": "Change Sorting", - "cardDetailsPopup-title": "Card Details", - "add-teams": "Add teams", - "add-teams-label": "Added teams are displayed below:", - "remove-team-from-table": "Are you sure you want to remove this team from the board ?", - "confirm-btn": "Confirm", - "remove-btn": "Remove", - "filter-card-title-label": "Filter by card title", - "invite-people-success": "Invitation to register sent with success", - "invite-people-error": "Error while sending invitation to register", - "can-invite-if-same-mailDomainName": "Email domain name", - "to-create-teams-contact-admin": "To create teams, please contact the administrator.", - "Node_heap_total_heap_size": "Node heap: total heap size", - "Node_heap_total_heap_size_executable": "Node heap: total heap size executable", - "Node_heap_total_physical_size": "Node heap: total physical size", - "Node_heap_total_available_size": "Node heap: total available size", - "Node_heap_used_heap_size": "Node heap: used heap size", - "Node_heap_heap_size_limit": "Node heap: heap size limit", - "Node_heap_malloced_memory": "Node heap: malloced memory", - "Node_heap_peak_malloced_memory": "Node heap: peak malloced memory", - "Node_heap_does_zap_garbage": "Node heap: does zap garbage", - "Node_heap_number_of_native_contexts": "Node heap: number of native contexts", - "Node_heap_number_of_detached_contexts": "Node heap: number of detached contexts", - "Node_memory_usage_rss": "Node memory usage: resident set size", - "Node_memory_usage_heap_total": "Node memory usage: total size of the allocated heap", - "Node_memory_usage_heap_used": "Node memory usage: actual memory used", - "Node_memory_usage_external": "Node memory usage: external", - "add-organizations": "Add organizations", - "add-organizations-label": "Added organizations are displayed below:", - "remove-organization-from-board": "Are you sure you want to remove this organization from this board ?", - "to-create-organizations-contact-admin": "To create organizations, please contact administrator.", - "custom-legal-notice-link-url": "Custom legal notice page URL", - "acceptance_of_our_legalNotice": "By continuing, you accept our", - "legalNotice": "legal notice", - "copied": "Copied!", - "checklistActionsPopup-title": "Checklist Actions", - "moveChecklist": "Move Checklist", - "moveChecklistPopup-title": "Move Checklist", - "newlineBecomesNewChecklistItem": "Each line of text becomes one of the checklist items", - "newLineNewItem": "One line of text = one checklist item", - "newlineBecomesNewChecklistItemOriginOrder": "Each line of text becomes one of the checklist items, original order", - "originOrder": "original order", - "copyChecklist": "Copy Checklist", - "copyChecklistPopup-title": "Copy Checklist", - "card-show-lists": "Card Show Lists", - "subtaskActionsPopup-title": "Subtask Actions", - "attachmentActionsPopup-title": "Attachment Actions", - "attachment-move-storage-fs": "Move attachment to filesystem", - "attachment-move-storage-gridfs": "Move attachment to GridFS", - "attachment-move-storage-s3": "Move attachment to S3", - "attachment-move": "Move Attachment", - "move-all-attachments-to-fs": "Move all attachments to filesystem", - "move-all-attachments-to-gridfs": "Move all attachments to GridFS", - "move-all-attachments-to-s3": "Move all attachments to S3", - "move-all-attachments-of-board-to-fs": "Move all attachments of board to filesystem", - "move-all-attachments-of-board-to-gridfs": "Move all attachments of board to GridFS", - "move-all-attachments-of-board-to-s3": "Move all attachments of board to S3", - "path": "Path", - "version-name": "Version-Name", - "size": "Size", - "storage": "Storage", - "action": "Action", - "board-title": "Board Title", - "attachmentRenamePopup-title": "Rename", - "uploading": "Uploading", - "remaining_time": "Remaining time", - "speed": "Speed", - "progress": "Progress", - "password-again": "Password (again)", - "if-you-already-have-an-account": "If you already have an account", - "register": "Register", - "forgot-password": "Forgot password", - "minicardDetailsActionsPopup-title": "Card Details", - "Mongo_sessions_count": "Mongo sessions count", - "change-visibility": "Change Visibility", - "max-upload-filesize": "Max upload filesize in bytes:", - "allowed-upload-filetypes": "Allowed upload filetypes:", - "max-avatar-filesize": "Max avatar filesize in bytes:", - "allowed-avatar-filetypes": "Allowed avatar filetypes:", - "invalid-file": "If filename is invalid, upload or rename is cancelled.", - "preview-pdf-not-supported": "Your device does not support previewing PDF. Try downloading instead.", - "drag-board": "Drag board", - "translation-number": "The number of custom translation strings is:", - "delete-translation-confirm-popup": "Are you sure you want to delete this custom translation string? There is no undo.", - "newTranslationPopup-title": "New custom translation string", - "editTranslationPopup-title": "Edit custom translation string", - "settingsTranslationPopup-title": "Delete this custom translation string?", - "translation": "Translation", - "text": "Text", - "translation-text": "Translation text", - "show-subtasks-field": "Show subtasks field", - "show-week-of-year": "Show week of year (ISO 8601)", - "convert-to-markdown": "Convert to markdown", - "import-board-zip": "Add .zip file that has board JSON files, and board name subdirectories with attachments", - "collapse": "Collapse", - "uncollapse": "Uncollapse", - "hideCheckedChecklistItems": "Hide checked checklist items", - "hideAllChecklistItems": "Hide all checklist items", - "support": "Support", - "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", - "accessibility": "Accessibility", - "accessibility-page-enabled": "Accessibility page enabled", - "accessibility-info-not-added-yet": "Accessibility info has not been added yet", - "accessibility-title": "Accessibility title", - "accessibility-content": "Accessibility content", - "accounts-lockout-settings": "Brute Force Protection Settings", - "accounts-lockout-info": "These settings control how login attempts are protected against brute force attacks.", - "accounts-lockout-known-users": "Settings for known users (correct username, wrong password)", - "accounts-lockout-unknown-users": "Settings for unknown users (non-existent username)", - "accounts-lockout-failures-before": "Failures before lockout", - "accounts-lockout-period": "Lockout period (seconds)", - "accounts-lockout-failure-window": "Failure window (seconds)", - "accounts-lockout-settings-updated": "Brute force protection settings have been updated", - "accounts-lockout-locked-users": "Locked Users", - "accounts-lockout-locked-users-info": "Users currently locked out due to too many failed login attempts", - "accounts-lockout-no-locked-users": "There are currently no locked users", - "accounts-lockout-failed-attempts": "Failed Attempts", - "accounts-lockout-remaining-time": "Remaining Time", - "accounts-lockout-user-unlocked": "User has been unlocked successfully", - "accounts-lockout-confirm-unlock": "Are you sure you want to unlock this user?", - "accounts-lockout-confirm-unlock-all": "Are you sure you want to unlock all locked users?", - "accounts-lockout-show-locked-users": "Show locked users only", - "accounts-lockout-user-locked": "User is locked", - "accounts-lockout-click-to-unlock": "Click to unlock this user", - "accounts-lockout-status": "Status", - "admin-people-filter-show": "Show:", - "admin-people-filter-all": "All Users", - "admin-people-filter-locked": "Locked Users Only", - "admin-people-filter-active": "Active", - "admin-people-filter-inactive": "Not Active", - "admin-people-active-status": "Active Status", - "admin-people-user-active": "User is active - click to deactivate", - "admin-people-user-inactive": "User is inactive - click to activate", - "accounts-lockout-all-users-unlocked": "All locked users have been unlocked", - "accounts-lockout-unlock-all": "Unlock All", - "active-cron-jobs": "Active Scheduled Jobs", - "add-cron-job": "Add Scheduled Job", - "add-cron-job-placeholder": "Add Scheduled Job functionality coming soon", - "attachment-storage-configuration": "Attachment Storage Configuration", - "attachments-path": "Attachments Path", - "attachments-path-description": "Path where attachment files are stored", - "avatars-path": "Avatars Path", - "avatars-path-description": "Path where avatar files are stored", - "board-archive-failed": "Failed to schedule board archive", - "board-archive-scheduled": "Board archive scheduled successfully", - "board-backup-failed": "Failed to schedule board backup", - "board-backup-scheduled": "Board backup scheduled successfully", - "board-cleanup-failed": "Failed to schedule board cleanup", - "board-cleanup-scheduled": "Board cleanup scheduled successfully", - "board-operations": "Board Operations", - "cron-jobs": "Scheduled Jobs", - "cron-migrations": "Scheduled Migrations", - "cron-job-delete-confirm": "Are you sure you want to delete this scheduled job?", - "cron-job-delete-failed": "Failed to delete scheduled job", - "cron-job-deleted": "Scheduled job deleted successfully", - "cron-job-pause-failed": "Failed to pause scheduled job", - "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", - "filesystem-path-description": "Base path for file storage", - "gridfs-enabled": "GridFS Enabled", - "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", - "migration-pause-failed": "Failed to pause migrations", - "migration-paused": "Migrations paused successfully", - "migration-progress": "Migration Progress", - "migration-start-failed": "Failed to start migrations", - "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", - "migration-status": "Migration Status", - "migration-stop-confirm": "Are you sure you want to stop all migrations?", - "migration-stop-failed": "Failed to stop migrations", - "migration-stopped": "Migrations stopped successfully", - "mongodb-gridfs-storage": "MongoDB GridFS Storage", - "pause-all-migrations": "Pause All Migrations", - "s3-access-key": "S3 Access Key", - "s3-access-key-description": "AWS S3 access key for authentication", - "s3-access-key-placeholder": "Enter S3 access key", - "s3-bucket": "S3 Bucket", - "s3-bucket-description": "S3 bucket name for storing files", - "s3-connection-failed": "S3 connection failed", - "s3-connection-success": "S3 connection successful", - "s3-enabled": "S3 Enabled", - "s3-enabled-description": "Use AWS S3 or MinIO for file storage", - "s3-endpoint": "S3 Endpoint", - "s3-endpoint-description": "S3 endpoint URL (e.g., s3.amazonaws.com or minio.example.com)", - "s3-minio-storage": "S3/MinIO Storage", - "s3-port": "S3 Port", - "s3-port-description": "S3 endpoint port number", - "s3-region": "S3 Region", - "s3-region-description": "AWS S3 region (e.g., us-east-1)", - "s3-secret-key": "S3 Secret Key", - "s3-secret-key-description": "AWS S3 secret key for authentication", - "s3-secret-key-placeholder": "Enter S3 secret key", - "s3-secret-key-required": "S3 secret key is required", - "s3-settings-save-failed": "Failed to save S3 settings", - "s3-settings-saved": "S3 settings saved successfully", - "s3-ssl-enabled": "S3 SSL Enabled", - "s3-ssl-enabled-description": "Use SSL/TLS for S3 connections", - "save-s3-settings": "Save S3 Settings", - "schedule-board-archive": "Schedule Board Archive", - "schedule-board-backup": "Schedule Board Backup", - "schedule-board-cleanup": "Schedule Board Cleanup", - "scheduled-board-operations": "Scheduled Board Operations", - "start-all-migrations": "Start All Migrations", - "stop-all-migrations": "Stop All Migrations", - "test-s3-connection": "Test S3 Connection", - "writable-path": "Writable Path", - "writable-path-description": "Base directory path for file storage", - "add-job": "Add Job", - "attachment-migration": "Attachment Migration", - "attachment-monitoring": "Attachment Monitoring", - "attachment-settings": "Attachment Settings", - "attachment-storage-settings": "Storage Settings", - "automatic-migration": "Automatic Migration", - "back-to-settings": "Back to Settings", - "board-id": "Board ID", - "board-migration": "Board Migration", - "board-migrations": "Board Migrations", - "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", - "cleanup": "Cleanup", - "cleanup-old-jobs": "Cleanup Old Jobs", - "completed": "Completed", - "conversion-info-text": "This conversion is performed once per board and improves performance. You can continue using the board normally.", - "converting-board": "Converting Board", - "converting-board-description": "Converting board structure for improved functionality. This may take a few moments.", - "cpu-cores": "CPU Cores", - "cpu-usage": "CPU Usage", - "current-action": "Current Action", - "database-migration": "Database Migration", - "database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.", - "database-migrations": "Database Migrations", - "days-old": "Days Old", - "duration": "Duration", - "errors": "Errors", - "estimated-time-remaining": "Estimated time remaining", - "every-1-day": "Every 1 day", - "every-1-hour": "Every 1 hour", - "every-1-minute": "Every 1 minute", - "every-10-minutes": "Every 10 minutes", - "every-30-minutes": "Every 30 minutes", - "every-5-minutes": "Every 5 minutes", - "every-6-hours": "Every 6 hours", - "export-monitoring": "Export Monitoring", - "filesystem-attachments": "Filesystem Attachments", - "filesystem-size": "Filesystem Size", - "filesystem-storage": "Filesystem Storage", - "force-board-scan": "Force Board Scan", - "gridfs-attachments": "GridFS Attachments", - "gridfs-size": "GridFS Size", - "gridfs-storage": "GridFS", - "hide-list-on-minicard": "Hide List on Minicard", - "idle-migration": "Idle Migration", - "job-description": "Job Description", - "job-details": "Job Details", - "job-name": "Job Name", - "job-queue": "Job Queue", - "last-run": "Last Run", - "max-concurrent": "Max Concurrent", - "memory-usage": "Memory Usage", - "migrate-all-to-filesystem": "Migrate All to Filesystem", - "migrate-all-to-gridfs": "Migrate All to GridFS", - "migrate-all-to-s3": "Migrate All to S3", - "migrated-attachments": "Migrated Attachments", - "migration-batch-size": "Batch Size", - "migration-batch-size-description": "Number of attachments to process in each batch (1-100)", - "migration-cpu-threshold": "CPU Threshold (%)", - "migration-cpu-threshold-description": "Pause migration when CPU usage exceeds this percentage (10-90)", - "migration-delay-ms": "Delay (ms)", - "migration-delay-ms-description": "Delay between batches in milliseconds (100-10000)", - "migration-detector": "Migration Detector", - "migration-info-text": "Database migrations are performed once and improve system performance. The process continues in the background even if you close your browser.", - "migration-log": "Migration Log", - "migration-markers": "Migration Markers", - "migration-resume-failed": "Failed to resume migration", - "migration-resumed": "Migration resumed", - "migration-steps": "Migration Steps", - "migration-warning-text": "Please do not close your browser during migration. The process will continue in the background but may take longer to complete.", - "monitoring-export-failed": "Failed to export monitoring data", - "monitoring-refresh-failed": "Failed to refresh monitoring data", - "next": "Next", - "next-run": "Next Run", - "of": "of", - "operation-type": "Operation Type", - "overall-progress": "Overall Progress", - "page": "Page", - "pause-migration": "Pause Migration", - "previous": "Previous", - "refresh": "Refresh", - "refresh-monitoring": "Refresh Monitoring", - "remaining-attachments": "Remaining Attachments", - "resume-migration": "Resume Migration", - "run-once": "Run once", - "s3-attachments": "S3 Attachments", - "s3-size": "S3 Size", - "s3-storage": "S3", - "scanning-status": "Scanning Status", - "schedule": "Schedule", - "search-boards-or-operations": "Search boards or operations...", - "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", - "showing": "Showing", - "start-test-operation": "Start Test Operation", - "start-time": "Start Time", - "step-progress": "Step Progress", - "stop-migration": "Stop Migration", - "storage-distribution": "Storage Distribution", - "system-resources": "System Resources", - "total-attachments": "Total Attachments", - "total-operations": "Total Operations", - "total-size": "Total Size", - "unmigrated-boards": "Unmigrated Boards", - "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" -} diff --git a/imports/i18n/data/sk.i18n.json b/imports/i18n/data/sk.i18n.json index f1d969304..4eff2776c 100644 --- a/imports/i18n/data/sk.i18n.json +++ b/imports/i18n/data/sk.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Pridať prílohu", @@ -98,7 +86,6 @@ "add-card": "Pridať kartu", "add-card-to-top-of-list": "Pridať Kartu na vrch listu", "add-card-to-bottom-of-list": "Pridať Kartu na spodok listu", - "addListPopup-title": "Pridať zoznam", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Pridať užívateľa", "added": "Predaný", - "addMemberPopup-title": "Pridať užívateľa", + "addMemberPopup-title": "Členovia", "memberPopup-title": "Member Settings", "admin": "Administrátor", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Oznámenie", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "Táto nástenka bude <strong>súkromná</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Zmeniť jazyk", "changePasswordPopup-title": "Zmeniť heslo", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Vytvoriť", "createBoardPopup-title": "Vytvoriť nástenku", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Vytvoriť štítok", "createCustomField": "Vytvoriť pole", @@ -385,7 +358,7 @@ "date": "Dátum", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Členovia", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Zmazať nástenku?", "delete-board": "Zmazať nástenku", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "Zobraziť všetko", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Karta", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Zoznam", "board": "Nástenka", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Čas", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Štart", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/sl.i18n.json b/imports/i18n/data/sl.i18n.json index aa3f5b0cc..7c7f0d684 100644 --- a/imports/i18n/data/sl.i18n.json +++ b/imports/i18n/data/sl.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "izbrisal komentar %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Predloge", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Dodaj priponko", @@ -98,7 +86,6 @@ "add-card": "Dodaj kartico", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Dodaj seznam", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Dodaj člane", "added": "Dodano", - "addMemberPopup-title": "Dodaj člane", + "addMemberPopup-title": "Člani", "memberPopup-title": "Nastavitve članov", "admin": "Administrator", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Lahko gleda in ureja kartice, odstrani člane ter spreminja nastavitve table.", "admin-announcement": "Najava", "admin-announcement-active": "Aktivna vse-sistemska najava", "admin-announcement-title": "Najava od administratorja", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s zvezdic", "board-not-found": "Tabla ni najdena", "board-private-info": "Ta tabla bo <strong>privatna</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Spremeni dovoljenja", "change-settings": "Spremeni nastavitve", "changeAvatarPopup-title": "Spremeni avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Spremeni jezik", "changePasswordPopup-title": "Spremeni geslo", "changePermissionsPopup-title": "Spremeni dovoljenja", @@ -335,16 +316,10 @@ "comment-placeholder": "Napiši komentar", "comment-only": "Samo komentar", "comment-only-desc": "Lahko komentirate samo na karticah.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "Ni komentarjev", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Ne morete videti komentarjev in dejavnosti.", "worker": "Delavec", "worker-desc": "Lahko samo premikam kartice, se dodelim na kartico in komentiram.", "computer": "Računalnik", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Kopiraj povezavo kartice na odložišče", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Poveži kartico", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"naslov\": \"Naslov prve kartice\", \"opis\":\"Opis prve kartice\"}, {\"naslov\":\"Opis druge kartice\",\"opis\":\"Opis druge kartice\"},{\"naslov\":\"Naslov zadnje kartice\",\"opis\":\"Opis zadnje kartice\"} ]", "create": "Ustvari", "createBoardPopup-title": "Ustvari tablo", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Uvozi tablo", "createLabelPopup-title": "Ustvari oznako", "createCustomField": "Ustvari polje", @@ -385,7 +358,7 @@ "date": "Datum", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Zavrni", "default-avatar": "Privzeti avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Lahko premaknete seznam v arhiv, da ga odstranite iz table in ohranite dejavnosti.", "lists": "Seznami", "swimlanes": "Plavalne steze", - "calendar": "Koledar", - "gantt": "Gantt", "log-out": "Odjava", "log-in": "Prijava", "loginPopup-title": "Prijava", "memberMenuPopup-title": "Nastavitve članov", - "grey-icons": "Grey Icons", "members": "Člani", "menu": "Meni", "move-selection": "Premakni izbiro", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Premakni na dno", "moveCardToTop-title": "Premakni na vrh", "moveSelectionPopup-title": "Premakni izbiro", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Izbira", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "Ni zadetkov", "normal": "Normalno", "normal-desc": "Lahko gleda in ureja kartice. Ne more spreminjati nastavitev.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Povabilo še ni sprejeto.", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Prejemajte posodobitve opazovanih tabel, seznamov ali kartic", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Dovoli spremembo e-poštnega naslova", "accounts-allowUserNameChange": "Dovoli spremembo up. imena", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Ustvarjen ob", "modifiedAt": "Modified at", "verified": "Preverjeno", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Spremeni datum prejema", "editCardEndDatePopup-title": "Spremeni končni datum", "setCardColorPopup-title": "Nastavi barvo", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Izberi barvo", "setSwimlaneColorPopup-title": "Izberi barvo", "setListColorPopup-title": "Izberi barvo", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Vsi seznami, kartice, oznake in dejavnosti bodo izbrisani in vsebine table ne boste mogli obnoviti. Razveljavitve ni.", "boardDeletePopup-title": "Izbriši tablo?", "delete-board": "Izbriši tablo", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Podopravila za tablo", @@ -839,7 +802,7 @@ "r-trigger": "Trigger", "r-action": "Action", "r-when-a-card": "Ko je kartica", - "r-is": "is", + "r-is": " ", "r-is-moved": "premaknjena", "r-added-to": "Added to", "r-removed-from": "izbrisan iz", @@ -942,13 +905,6 @@ "authentication-method": "Metoda avtentikacije", "authentication-type": "Način avtentikacije", "custom-product-name": "Ime izdelka po meri", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Postavitev", "hide-logo": "Skrij logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "spremenil končni čas v", "a-startAt": "spremenil začetni čas v", "a-receivedAt": "spremenil čas prejetja v", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "trenutni rok %s se približuje", "pastdue": "trenutni rok %s je potekel", "duenow": "trenutni rok %s je danes", @@ -989,7 +943,7 @@ "act-almostdue": "je opomnil trenuten rok zapadlosti (__timeValue__) kartice __card__ se bliža", "act-pastdue": "je opomnil trenuten rok zapadlosti (__timeValue__) kartice __card__ je potekel", "act-duenow": "je opomnil trenuten rok zapadlosti (__timeValue__) kartice __card__ je sedaj", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Omenjeni ste bili v [__board__] __list__/__card__", "delete-user-confirm-popup": "Ali ste prepričani, da želite izbrisati ta račun? Razveljavitve ni.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Kartica", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Dostopnost", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Čas", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Začetek", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "zaključen", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/sl_SI.i18n.json b/imports/i18n/data/sl_SI.i18n.json deleted file mode 100644 index aa3f5b0cc..000000000 --- a/imports/i18n/data/sl_SI.i18n.json +++ /dev/null @@ -1,1686 +0,0 @@ -{ - "accept": "Sprejmi", - "act-activity-notify": "Activity Notification", - "act-addAttachment": "dodal priponko __attachment__ h kartici __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-deleteAttachment": "odstranil priponko __attachment__ iz kartice __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-addSubtask": "dodal podopravilo __subtask__ h kartici __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-addLabel": "Dodal oznako __label__ h kartici __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-addedLabel": "Dodal oznako __label__ h kartici __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-removeLabel": "Odstranil oznako __label__ iz kartice __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-removedLabel": "Odstranil oznako __label__ iz kartice __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-addChecklist": "dodal kontrolni seznam __checklist__ h kartici __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-addChecklistItem": "dodal postavko __checklistItem__ kontrolnega seznama __checklist__ na kartici __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-removeChecklist": "odstranil kontrolni seznam __checklist__ iz kartice __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-removeChecklistItem": "odstranil postavko __checklistItem__ kontrolnega seznama __checklist__ na kartici __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-checkedItem": "obkljukal postavko __checklistItem__ kontrolnega seznama __checklist__ na kartici __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-uncheckedItem": "odkljukal postavko __checklistItem__ kontrolnega seznama __checklist__ na kartici __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncompleteChecklist": "nedokončan kontrolni seznam __checklist__ na kartici __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-addComment": "komentiral na kartici __card__: __comment__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-editComment": "uredil komentar na kartici __card__: __comment__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-deleteComment": "izbrisal komentar na kartici __card__: __comment__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-createBoard": "ustvaril tablo __board__", - "act-createSwimlane": "ustvaril plavalno stezo __swimlane__ na tabli __board__", - "act-createCard": "ustvaril kartico __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-createCustomField": "ustvaril poljubno polje __customField__ na tabli __board__", - "act-deleteCustomField": "izbrisal poljubno polje __customField__ na tabli __board__", - "act-setCustomField": "uredil poljubno polje __customField__: __customFieldValue__ na kartici __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-createList": "dodal seznam __list__ na tablo __board__", - "act-addBoardMember": "dodal člana __member__ k tabli __board__", - "act-archivedBoard": "Tabla __board__ premaknjena v arhiv", - "act-archivedCard": "Kartica __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__ premaknjena v arhiv", - "act-archivedList": "Seznam __list__ na plavalni stezi __swimlane__ na tabli __board__ premaknjen v arhiv", - "act-archivedSwimlane": "Plavalna steza __swimlane__ na tabli __board__ premaknjena v arhiv", - "act-importBoard": "uvozil tablo __board__", - "act-importCard": "uvozil kartico __card__ na seznam __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-importList": "uvozil seznam __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-joinMember": "dodal član __member__ h kartici __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-moveCard": "premakil kartico __card__ na tabli __board__ iz seznama __oldList__ na plavalni stezi __oldSwimlane__ na seznam __list__ na plavalni stezi __swimlane__", - "act-moveCardToOtherBoard": "premaknil kartico __card__ iz seznama __oldList__ na plavalni stezi __oldSwimlane__ na tabli __oldBoard__ na seznam __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-removeBoardMember": "odstranil člana __member__ iz table __board__", - "act-restoredCard": "obnovil kartico __card__ na seznam __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-unjoinMember": "odstranil člana __member__ iz kartice __card__ na seznamu __list__ na plavalni stezi __swimlane__ na tabli __board__", - "act-withBoardTitle": "__board__", - "act-withCardTitle": "[__board__] __card__", - "actions": "Dejanja", - "activities": "Aktivnosti", - "activity": "Aktivnost", - "activity-added": "dodal %s v %s", - "activity-archived": "%s premaknjeno v arhiv", - "activity-attached": "pripel %s v %s", - "activity-created": "ustvaril %s", - "activity-changedListTitle": "renamed list to %s", - "activity-customfield-created": "ustvaril poljubno polje%s", - "activity-excluded": "izključil %s iz %s", - "activity-imported": "uvozil %s v %s iz %s", - "activity-imported-board": "uvozil %s iz %s", - "activity-joined": "se je pridružil na %s", - "activity-moved": "premakil %s iz %s na %s", - "activity-on": "na %s", - "activity-removed": "odstranil %s iz %s", - "activity-sent": "poslano %s na %s", - "activity-unjoined": "se je odjavil iz %s", - "activity-subtask-added": "dodal podopravilo k %s", - "activity-checked-item": "obkljukal %s na kontrolnem seznamu %s od %s", - "activity-unchecked-item": "odkljukal %s na kontrolnem seznamu %s od %s", - "activity-checklist-added": "dodal kontrolni seznam na %s", - "activity-checklist-removed": "odstranil kontrolni seznam iz %s", - "activity-checklist-completed": "dokončan kontrolni seznam %s od %s", - "activity-checklist-uncompleted": "nedokončal kontrolni seznam %s od %s", - "activity-checklist-item-added": "dodal postavko kontrolnega seznama na '%s' v %s", - "activity-checklist-item-removed": "odstranil postavko kontrolnega seznama iz '%s' v %s", - "add": "Dodaj", - "activity-checked-item-card": "obkljukal %s na kontrolnem seznamu %s", - "activity-unchecked-item-card": "odkljukal %s na kontrolnem seznamu %s", - "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "activity-checklist-uncompleted-card": "nedokončal kontrolni seznam %s", - "activity-editComment": "uredil komentar %s", - "activity-deleteComment": "izbrisal komentar %s", - "activity-receivedDate": "edited received date to %s of %s", - "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Predloge", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", - "activity-dueDate": "edited due date to %s of %s", - "activity-endDate": "edited end date to %s of %s", - "add-attachment": "Dodaj priponko", - "add-board": "Dodaj tablo", - "add-template": "Add Template", - "add-card": "Dodaj kartico", - "add-card-to-top-of-list": "Add Card to Top of List", - "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Dodaj seznam", - "setListWidthPopup-title": "Set Widths", - "set-list-width": "Set Widths", - "set-list-width-value": "Set Min & Max Widths (pixels)", - "list-width-error-message": "List widths must be integers greater than 100", - "keyboard-shortcuts-enabled": "Keyboard shortcuts enabled. Click to disable.", - "keyboard-shortcuts-disabled": "Keyboard shortcuts disabled. Click to enable.", - "setSwimlaneHeightPopup-title": "Set Swimlane Height", - "set-swimlane-height": "Set Swimlane Height", - "set-swimlane-height-value": "Swimlane Height (pixels)", - "swimlane-height-error-message": "Swimlane height must be a positive integer", - "add-swimlane": "Dodaj plavalno stezo", - "add-subtask": "Dodaj podopravilo", - "add-checklist": "Dodaj kontrolni seznam", - "add-checklist-item": "Dodaj postavko na kontrolni seznam", - "close-add-checklist-item": "Close add an item to checklist form", - "close-edit-checklist-item": "Close edit an item to checklist form", - "convertChecklistItemToCardPopup-title": "Convert to Card", - "add-cover": "Add cover image to minicard", - "add-label": "Dodaj oznako", - "add-list": "Dodaj seznam", - "add-after-list": "Add After List", - "add-members": "Dodaj člane", - "added": "Dodano", - "addMemberPopup-title": "Dodaj člane", - "memberPopup-title": "Nastavitve članov", - "admin": "Administrator", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", - "admin-announcement": "Najava", - "admin-announcement-active": "Aktivna vse-sistemska najava", - "admin-announcement-title": "Najava od administratorja", - "all-boards": "Vse table", - "and-n-other-card": "In __count__ druga kartica", - "and-n-other-card_plural": "In __count__ drugih kartic", - "apply": "Uporabi", - "app-is-offline": "Nalaganje, prosimo počakajte. Osveževanje strani bo povzročilo izgubo podatkov. Če nalaganje ne deluje, preverite, ali se strežnik ni ustavil.", - "app-try-reconnect": "Try to reconnect.", - "archive": "premaknjena v arhiv", - "archive-all": "Premakni vse v arhiv", - "archive-board": "Arhiviraj tablo", - "archive-board-confirm": "Are you sure you want to archive this board?", - "archive-card": "Arhiviraj kartico", - "archive-list": "Arhiviraj seznam", - "archive-swimlane": "Arhiviraj plavalno stezo", - "archive-selection": "Arhiviraj označeno", - "archiveBoardPopup-title": "Arhiviraj tablo?", - "archived-items": "Arhiv", - "archived-boards": "Table v arhivu", - "restore-board": "Obnovi tablo", - "no-archived-boards": "Nobene table ni v arhivu.", - "archives": "Arhiv", - "template": "Predloga", - "templates": "Predloge", - "template-container": "Template Container", - "add-template-container": "Add Template Container", - "assign-member": "Dodeli člana", - "attached": "pripeto", - "attachment": "Priponka", - "attachment-delete-pop": "Brisanje priponke je trajno. Ne obstaja razveljavitev.", - "attachmentDeletePopup-title": "Briši priponko?", - "attachments": "Priponke", - "auto-watch": "Samodejno spremljaj ustvarjene table", - "avatar-too-big": "The avatar is too large (__size__ max)", - "back": "Nazaj", - "board-change-color": "Spremeni barvo", - "board-change-background-image": "Change Background Image", - "board-background-image-url": "Background Image URL", - "add-background-image": "Add Background Image", - "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", - "boardInfoOnMyBoards-title": "All Boards Settings", - "show-card-counter-per-list": "Show card count per list", - "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", - "board-nb-stars": "%s zvezdic", - "board-not-found": "Tabla ni najdena", - "board-private-info": "Ta tabla bo <strong>privatna</strong>.", - "board-public-info": "Ta tabla bo <strong>javna</strong>.", - "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", - "boardChangeColorPopup-title": "Spremeni ozadje table", - "boardChangeBackgroundImagePopup-title": "Change Background Image", - "allBoardsChangeColorPopup-title": "Spremeni barvo", - "allBoardsChangeBackgroundImagePopup-title": "Change Background Image", - "boardChangeTitlePopup-title": "Preimenuj tablo", - "boardChangeVisibilityPopup-title": "Spremeni vidnost", - "boardChangeWatchPopup-title": "Spremeni opazovanje", - "boardMenuPopup-title": "Nastavitve table", - "allBoardsMenuPopup-title": "Nastavitve", - "boardChangeViewPopup-title": "Pogled table", - "boards": "Table", - "board-view": "Pogled table", - "desktop-mode": "Desktop Mode", - "mobile-mode": "Mobile Mode", - "mobile-desktop-toggle": "Toggle between Mobile and Desktop Mode", - "zoom-in": "Zoom In", - "zoom-out": "Zoom Out", - "click-to-change-zoom": "Click to change zoom level", - "zoom-level": "Zoom Level", - "enter-zoom-level": "Enter zoom level (50-300%):", - "board-view-cal": "Koledar", - "board-view-swimlanes": "Plavalne steze", - "board-view-collapse": "Skrči", - "board-view-gantt": "Gantt", - "board-view-lists": "Seznami", - "bucket-example": "Like \"Bucket List\" for example", - "calendar-previous-month-label": "Previous Month", - "calendar-next-month-label": "Next Month", - "cancel": "Prekliči", - "card-archived": "Kartica je premaknjena v arhiv.", - "board-archived": "Tabla je premaknjena v arhiv.", - "card-comments-title": "Ta kartica ima %s komentar.", - "card-delete-notice": "Brisanje je trajno. Izgubili boste vsa dejanja, povezana s kartico.", - "card-delete-pop": "Vsa dejanja bodo odstranjena iz zgodovine dejavnosti. Kartice ne boste mogli znova odpreti. Razveljavitve ni.", - "card-delete-suggest-archive": "Kartico lahko premaknete v arhiv, da jo odstranite s table in ohranite dejavnost.", - "card-archive-pop": "Card will not be visible at this list after archiving card.", - "card-archive-suggest-cancel": "You can later restore card from Archive.", - "card-due": "Due", - "card-due-on": "Rok", - "card-spent": "Porabljen čas", - "card-edit-attachments": "Uredi priponke", - "card-edit-custom-fields": "Uredi poljubna polja", - "card-edit-labels": "Uredi oznake", - "card-edit-members": "Uredi člane", - "card-labels-title": "Spremeni oznake za kartico.", - "card-members-title": "Dodaj ali odstrani člane table iz kartice.", - "card-start": "Začetek", - "card-start-on": "Začne ob", - "cardAttachmentsPopup-title": "Pripni od", - "cardCustomField-datePopup-title": "Spremeni datum", - "cardCustomFieldsPopup-title": "Uredi poljubna polja", - "cardStartVotingPopup-title": "Start a vote", - "positiveVoteMembersPopup-title": "Proponents", - "negativeVoteMembersPopup-title": "Opponents", - "card-edit-voting": "Edit voting", - "editVoteEndDatePopup-title": "Change vote end date", - "allowNonBoardMembers": "Allow all logged in users", - "vote-question": "Voting question", - "vote-public": "Show who voted what", - "vote-for-it": "for it", - "vote-against": "against", - "deleteVotePopup-title": "Delete vote?", - "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", - "cardStartPlanningPokerPopup-title": "Start a Planning Poker", - "card-edit-planning-poker": "Edit Planning Poker", - "editPokerEndDatePopup-title": "Change Planning Poker vote end date", - "poker-question": "Planning Poker", - "poker-one": "1", - "poker-two": "2", - "poker-three": "3", - "poker-five": "5", - "poker-eight": "8", - "poker-thirteen": "13", - "poker-twenty": "20", - "poker-forty": "40", - "poker-oneHundred": "100", - "poker-unsure": "?", - "poker-finish": "Finish", - "poker-result-votes": "Votes", - "poker-result-who": "Who", - "poker-replay": "Replay", - "set-estimation": "Set Estimation", - "deletePokerPopup-title": "Delete planning poker?", - "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", - "cardDeletePopup-title": "Briši kartico?", - "cardArchivePopup-title": "Archive Card?", - "cardDetailsActionsPopup-title": "Dejanja kartice", - "cardLabelsPopup-title": "Oznake", - "cardMembersPopup-title": "Člani", - "cardMorePopup-title": "Več", - "cardTemplatePopup-title": "Ustvari predlogo", - "cards": "Kartic", - "cards-count": "Kartic", - "cards-count-one": "Kartica", - "casSignIn": "Vpiši se s CAS", - "cardType-card": "Kartica", - "cardType-linkedCard": "Povezana kartica", - "cardType-linkedBoard": "Povezana tabla", - "change": "Spremeni", - "change-avatar": "Spremeni avatar", - "change-password": "Spremeni geslo", - "change-permissions": "Spremeni dovoljenja", - "change-settings": "Spremeni nastavitve", - "changeAvatarPopup-title": "Spremeni avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", - "changeLanguagePopup-title": "Spremeni jezik", - "changePasswordPopup-title": "Spremeni geslo", - "changePermissionsPopup-title": "Spremeni dovoljenja", - "changeSettingsPopup-title": "Spremeni nastavitve", - "subtasks": "Podopravila", - "checklists": "Kontrolni seznami", - "click-to-star": "Kliknite, da označite tablo z zvezdico.", - "click-to-unstar": "Kliknite, da odznačite tablo z zvezdico.", - "click-to-enable-auto-width": "Auto list width disabled. Click to enable.", - "click-to-disable-auto-width": "Auto list width enabled. Click to disable.", - "auto-list-width": "Auto list width", - "clipboard": "Odložišče ali povleci & spusti", - "close": "Zapri", - "close-board": "Zapri tablo", - "close-board-pop": "Tablo boste lahko obnovili s klikom na gumb »Arhiviraj« na vstopni strani.", - "close-card": "Close Card", - "color-black": "črna", - "color-blue": "modra", - "color-crimson": "temno rdeča", - "color-darkgreen": "temno zelena", - "color-gold": "zlata", - "color-gray": "siva", - "color-green": "zelena", - "color-indigo": "indigo", - "color-lime": "limeta", - "color-magenta": "magenta", - "color-mistyrose": "rožnata", - "color-navy": "navy modra", - "color-orange": "oranžna", - "color-paleturquoise": "bledo turkizna", - "color-peachpuff": "breskvasta", - "color-pink": "roza", - "color-plum": "slivova", - "color-purple": "vijolična", - "color-red": "rdeča", - "color-saddlebrown": "rjava", - "color-silver": "srebrna", - "color-sky": "nebesna", - "color-slateblue": "skrilasto modra", - "color-white": "bela", - "color-yellow": "rumena", - "unset-color": "Onemogoči", - "comments": "Comments", - "comment": "Komentiraj", - "comment-placeholder": "Napiši komentar", - "comment-only": "Samo komentar", - "comment-only-desc": "Lahko komentirate samo na karticah.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", - "comment-delete": "Are you sure you want to delete the comment?", - "deleteCommentPopup-title": "Delete comment?", - "no-comments": "Ni komentarjev", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", - "worker": "Delavec", - "worker-desc": "Lahko samo premikam kartice, se dodelim na kartico in komentiram.", - "computer": "Računalnik", - "confirm-subtask-delete-popup": "Ste prepričani, da želite izbrisati podopravilo?", - "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", - "subtaskDeletePopup-title": "Delete Subtask?", - "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", - "copy-card-link-to-clipboard": "Kopiraj povezavo kartice na odložišče", - "copy-text-to-clipboard": "Copy text to clipboard", - "linkCardPopup-title": "Poveži kartico", - "searchElementPopup-title": "Išči", - "copyCardPopup-title": "Kopiraj kartico", - "copyManyCardsPopup-title": "Copy Template to Many Cards", - "copyManyCardsPopup-instructions": "Naslovi ciljnih kartic in opisi v JSON formatu", - "copyManyCardsPopup-format": "[ {\"naslov\": \"Naslov prve kartice\", \"opis\":\"Opis prve kartice\"}, {\"naslov\":\"Opis druge kartice\",\"opis\":\"Opis druge kartice\"},{\"naslov\":\"Naslov zadnje kartice\",\"opis\":\"Opis zadnje kartice\"} ]", - "create": "Ustvari", - "createBoardPopup-title": "Ustvari tablo", - "createTemplateContainerPopup-title": "Add Template Container", - "chooseBoardSourcePopup-title": "Uvozi tablo", - "createLabelPopup-title": "Ustvari oznako", - "createCustomField": "Ustvari polje", - "createCustomFieldPopup-title": "Ustvari polje", - "current": "trenutno", - "custom-field-delete-pop": "Razveljavitve ni. To bo odstranilo to poljubno polje iz vseh kartic in izbrisalo njegovo zgodovino.", - "custom-field-checkbox": "Potrditveno polje", - "custom-field-currency": "Currency", - "custom-field-currency-option": "Currency Code", - "custom-field-date": "Datum", - "custom-field-dropdown": "Spustni seznam", - "custom-field-dropdown-none": "(nobeno)", - "custom-field-dropdown-options": "Možnosti seznama", - "custom-field-dropdown-options-placeholder": "Pritisnite enter da dodate več možnosti", - "custom-field-dropdown-unknown": "(neznano)", - "custom-field-number": "Število", - "custom-field-text": "Besedilo", - "custom-fields": "Poljubna polja", - "date": "Datum", - "date-format": "Date Format", - "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", - "date-format-mm-dd-yyyy": "MM-DD-YYYY", - "decline": "Zavrni", - "default-avatar": "Privzeti avatar", - "delete": "Briši", - "deleteCustomFieldPopup-title": "Briši poljubno polje?", - "deleteLabelPopup-title": "Briši oznako?", - "description": "Opis", - "disambiguateMultiLabelPopup-title": "Razdvoji Dejanje Oznake", - "disambiguateMultiMemberPopup-title": "Razdvoji dejanje člana", - "discard": "Razveljavi", - "done": "Končano", - "download": "Prenos", - "edit": "Uredi", - "edit-avatar": "Spremeni avatar", - "edit-profile": "Uredi profil", - "edit-wip-limit": "Uredi omejitev št. kartic", - "soft-wip-limit": "Omehčaj omejitev št. kartic", - "editCardStartDatePopup-title": "Spremeni začetni datum", - "editCardDueDatePopup-title": "Spremeni datum zapadlosti", - "editCustomFieldPopup-title": "Uredi polje", - "addReactionPopup-title": "Add reaction", - "editCardSpentTimePopup-title": "Spremeni porabljen čas", - "editLabelPopup-title": "Spremeni oznako", - "editNotificationPopup-title": "Uredi obvestilo", - "editProfilePopup-title": "Uredi profil", - "email": "E-pošta", - "email-address": "Email Address", - "email-enrollAccount-subject": "Up. račun ustvarjen za vas na __siteName__", - "email-enrollAccount-text": "Pozdravljeni __user__,\n\nZa začetek uporabe kliknite spodnjo povezavo.\n\n__url__\n\nHvala.", - "email-fail": "Pošiljanje e-pošte ni uspelo", - "email-fail-text": "Napaka pri poskusu pošiljanja e-pošte", - "email-invalid": "Neveljavna e-pošta", - "email-invite": "Povabi z uporabo e-pošte", - "email-invite-subject": "__inviter__ vam je poslal povabilo", - "email-invite-text": "Spoštovani __user__,\n\n__inviter__ vas vabi k sodelovanju na tabli \"__board__\".\n\nProsimo sledite spodnji povezavi:\n\n__url__\n\nHvala.", - "email-resetPassword-subject": "Ponastavite geslo na __siteName__", - "email-resetPassword-text": "Pozdravljeni __user__,\n\nZa ponastavitev gesla kliknite na spodnjo povezavo.\n\n__url__\n\nHvala.", - "email-sent": "E-pošta poslana", - "email-verifyEmail-subject": "Preverite svoje e-poštni naslov na __siteName__", - "email-verifyEmail-text": "Pozdravljeni __user__,\n\nDa preverite e-poštni naslov za vaš uporabniški račun, kliknite na spodnjo povezavo.\n\n__url__\n\nHvala.", - "enable-vertical-scrollbars": "Enable vertical scrollbars", - "enable-wip-limit": "Vklopi omejitev št. kartic", - "error-board-doesNotExist": "Ta tabla ne obstaja", - "error-board-notAdmin": "Nimate administrativnih pravic za tablo.", - "error-board-notAMember": "Niste član table.", - "error-json-malformed": "Vaše besedilo ni veljaven JSON", - "error-json-schema": "Vaši JSON podatki ne vsebujejo pravilnih informacij v ustreznem formatu", - "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format ", - "error-list-doesNotExist": "Seznam ne obstaja", - "error-user-doesNotExist": "Uporabnik ne obstaja", - "error-user-notAllowSelf": "Ne morete povabiti sebe", - "error-user-notCreated": "Ta uporabnik ni ustvarjen", - "error-username-taken": "To up. ime že obstaja", - "error-orgname-taken": "This organization name is already taken", - "error-teamname-taken": "This team name is already taken", - "error-email-taken": "E-poštni naslov je že zaseden", - "export-board": "Izvozi tablo", - "export-board-json": "Export board to JSON", - "export-board-csv": "Export board to CSV", - "export-board-tsv": "Export board to TSV", - "export-board-excel": "Export board to Excel", - "user-can-not-export-excel": "User can not export Excel", - "export-board-html": "Export board to HTML", - "export-card": "Export card", - "export-card-pdf": "Export card to PDF", - "user-can-not-export-card-to-pdf": "User can not export card to PDF", - "exportBoardPopup-title": "Izvozi tablo", - "exportCardPopup-title": "Export card", - "sort": "Sortiraj", - "sorted": "Sorted", - "remove-sort": "Remove sort", - "sort-desc": "Klikni za sortiranje seznama", - "list-sort-by": "Sortiraj po:", - "list-label-modifiedAt": "Nazadnje dostopano", - "list-label-title": "Ime seznama", - "list-label-sort": "Ročno nastavljen vrstni red", - "list-label-short-modifiedAt": "(N)", - "list-label-short-title": "(I)", - "list-label-short-sort": "(R)", - "filter": "Filtriraj", - "filter-cards": "Filtriraj kartice ali sezname", - "filter-dates-label": "Filter by date", - "filter-no-due-date": "No due date", - "filter-overdue": "Overdue", - "filter-due-today": "Due today", - "filter-due-this-week": "Due this week", - "filter-due-next-week": "Due next week", - "filter-due-tomorrow": "Due tomorrow", - "list-filter-label": "Filtriraj seznam po imenu", - "filter-clear": "Počisti filter", - "filter-labels-label": "Filter by label", - "filter-no-label": "Brez oznake", - "filter-member-label": "Filter by member", - "filter-no-member": "Brez člana", - "filter-assignee-label": "Filter by assignee", - "filter-no-assignee": "No assignee", - "filter-custom-fields-label": "Filter by Custom Fields", - "filter-no-custom-fields": "Brez poljubnih polj", - "filter-show-archive": "Prikaži arhivirane sezname", - "filter-hide-empty": "Skrij prazne sezname", - "filter-on": "Filter vklopljen", - "filter-on-desc": "Filtrirane kartice na tej tabli. Kliknite tukaj za urejanje filtra.", - "filter-to-selection": "Filtriraj izbrane", - "other-filters-label": "Other Filters", - "advanced-filter-label": "Napredni filter", - "advanced-filter-description": "Napredni filter omogoča pripravo niza, ki vsebuje naslednje operaterje: == != <= >= && || () Preslednica se uporablja kot ločilo med operatorji. Vsa polja po meri lahko filtrirate tako, da vtipkate njihova imena in vrednosti. Na primer: Polje1 == Vrednost1. Opomba: Če polja ali vrednosti vsebujejo presledke, jih morate postaviti v enojne narekovaje. Primer: 'Polje 1' == 'Vrednost 1'. Če želite preskočiti posamezne kontrolne znake (' \\\\/), lahko uporabite \\\\\\. Na primer: Polje1 == I\\\\'m. Prav tako lahko kombinirate več pogojev. Na primer: F1 == V1 || F1 == V2. Običajno se vsi operaterji interpretirajo od leve proti desni. Vrstni red lahko spremenite tako, da postavite oklepaje. Na primer: F1 == V1 && ( F2 == V2 || F2 == V3 ). Prav tako lahko po besedilu iščete z uporabo pravil regex: F1 == /Tes.*/i", - "fullname": "Polno Ime", - "header-logo-title": "Pojdi nazaj na stran s tablami.", - "show-activities": "Show Activities", - "headerBarCreateBoardPopup-title": "Ustvari tablo", - "home": "Domov", - "import": "Uvozi", - "impersonate-user": "Impersonate user", - "link": "Poveži", - "import-board": "uvozi tablo", - "import-board-c": "Uvozi tablo", - "import-board-title-trello": "Uvozi tablo iz orodja Trello", - "import-board-title-wekan": "Uvozi tablo iz prejšnjega izvoza", - "import-board-title-csv": "Import board from CSV/TSV", - "from-trello": "Iz orodja Trello", - "from-wekan": "Od prejšnjega izvoza", - "from-csv": "From CSV/TSV", - "import-board-instruction-trello": "V vaši Trello tabli pojdite na 'Meni', 'Več', 'Natisni in Izvozi', 'Izvozi JSON', in kopirajte prikazano besedilo.", - "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", - "import-board-instruction-wekan": "V vaši tabli pojdite na 'Meni', 'Izvozi tablo' in kopirajte besedilo iz prenesene datoteke.", - "import-board-instruction-about-errors": "Pri napakah med uvozom table v nekaterih primerih uvažanje še deluje, uvožena tabla pa je na strani Vse Table.", - "import-json-placeholder": "Tukaj prilepite veljavne JSON podatke", - "import-csv-placeholder": "Paste your valid CSV/TSV data here", - "import-map-members": "Mapiraj člane", - "import-members-map": "Vaša uvožena tabla vsebuje nekaj članov. Prosimo mapirajte člane, ki jih želite uvoziti, z vašimi uporabniki.", - "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", - "import-show-user-mapping": "Preglejte povezane člane", - "import-user-select": "Izberite obstoječega uporabnika, ki ga želite uporabiti kot tega člana.", - "importMapMembersAddPopup-title": "Izberite člana", - "info": "Različica", - "initials": "Inicialke", - "invalid-date": "Neveljaven datum", - "invalid-time": "Neveljaven čas", - "invalid-user": "Neveljaven uporabnik", - "joined": "se je pridružil", - "just-invited": "Povabljeni ste k tej tabli", - "keyboard-shortcuts": "Bližnjice", - "label-create": "Ustvari oznako", - "label-default": "%s oznaka (privzeto)", - "label-delete-pop": "Razveljavitve ni. To bo odstranilo oznako iz vseh kartic in izbrisalo njeno zgodovino.", - "labels": "Oznake", - "language": "Jezik", - "last-admin-desc": "Ne morete zamenjati vlog, ker mora obstajati vsaj en admin.", - "leave-board": "Zapusti tablo", - "leave-board-pop": "Ste prepričani, da želite zapustiti tablo __boardTitle__? Odstranjeni boste iz vseh kartic na tej tabli.", - "leaveBoardPopup-title": "Zapusti tablo ?", - "link-card": "Poveži s kartico", - "list-archive-cards": "Arhiviraj vse kartice v seznamu", - "list-archive-cards-pop": "To bo odstranilo vse kartice tega seznama. Za ogled in vrnitev kartic iz arhiva na tablo, kliknite \"Meni\" > \"arhiv\".", - "list-move-cards": "Premakni vse kartice na seznamu", - "list-select-cards": "Izberi vse kartice na seznamu", - "set-color-list": "Nastavi barvo", - "listActionPopup-title": "Dejanja seznama", - "settingsUserPopup-title": "User Settings", - "settingsTeamPopup-title": "Team Settings", - "settingsOrgPopup-title": "Organization Settings", - "swimlaneActionPopup-title": "Dejanja plavalnih stez", - "swimlaneAddPopup-title": "Dodaj plavalno stezo spodaj", - "listImportCardPopup-title": "Uvozi Trello kartico", - "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", - "listMorePopup-title": "Več", - "link-list": "Poveži s seznamom", - "list-delete-pop": "Vsa dejanja bodo odstranjena iz vira dejavnosti in seznama ne boste mogli obnoviti. Razveljavitve ni.", - "list-delete-suggest-archive": "Lahko premaknete seznam v arhiv, da ga odstranite iz table in ohranite dejavnosti.", - "lists": "Seznami", - "swimlanes": "Plavalne steze", - "calendar": "Koledar", - "gantt": "Gantt", - "log-out": "Odjava", - "log-in": "Prijava", - "loginPopup-title": "Prijava", - "memberMenuPopup-title": "Nastavitve članov", - "grey-icons": "Grey Icons", - "members": "Člani", - "menu": "Meni", - "move-selection": "Premakni izbiro", - "moveCardPopup-title": "Premakni kartico", - "moveCardToBottom-title": "Premakni na dno", - "moveCardToTop-title": "Premakni na vrh", - "moveSelectionPopup-title": "Premakni izbiro", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", - "multi-selection": "Multi-Izbira", - "multi-selection-label": "Set label for selection", - "multi-selection-member": "Set member for selection", - "multi-selection-on": "Multi-Izbira je omogočena", - "muted": "Utišano", - "muted-info": "O spremembah na tej tabli ne boste prejemali obvestil.", - "my-boards": "Moje Table", - "name": "Ime", - "no-archived-cards": "Ni kartic v arhivu", - "no-archived-lists": "Ni seznamov v arhivu", - "no-archived-swimlanes": "Ni plavalnih stez v arhivu", - "no-results": "Ni zadetkov", - "normal": "Normalno", - "normal-desc": "Lahko gleda in ureja kartice. Ne more spreminjati nastavitev.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", - "not-accepted-yet": "Povabilo še ni sprejeto.", - "notify-participate": "Receive updates to any cards you participate as creator or member", - "notify-watch": "Prejemajte posodobitve opazovanih tabel, seznamov ali kartic", - "optional": "opcijsko", - "or": "ali", - "page-maybe-private": "Ta stran je morda privatna. Verjetno si jo lahko ogledate po<a href='%s'>prijavi</a>.", - "page-not-found": "Stran ne obstaja.", - "password": "Geslo", - "paste-or-dragdrop": "prilepi ali povleci & spusti datoteko slike (samo slika)", - "participating": "Sodelovanje", - "preview": "Predogled", - "previewAttachedImagePopup-title": "Predogled", - "previewClipboardImagePopup-title": "Predogled", - "private": "Zasebno", - "private-desc": "Ta tabla je zasebna. Vsebino lahko vidijo ali urejajo samo dodani uporabniki.", - "profile": "Profil", - "public": "Javno", - "public-desc": "Ta tabla je javna. Vidna je vsakomur s povezavo do table in bo prikazana v zadetkih iskalnikov kot Google. Urejajo jo lahko samo člani table.", - "quick-access-description": "Če tablo označite z zvezdico, bo tukaj dodana bližnjica.", - "remove-cover": "Remove cover image from minicard", - "remove-from-board": "Odstrani iz table", - "remove-label": "Odstrani oznako", - "listDeletePopup-title": "Odstrani seznam?", - "remove-member": "Odstrani člana", - "remove-member-from-card": "Odstrani iz kartice", - "remove-member-pop": "Odstrani __name__ (__username__) iz __boardTitle__? Član bo odstranjen iz vseh kartic te table in bo prejel obvestilo.", - "removeMemberPopup-title": "Odstrani člana?", - "rename": "Preimenuj", - "rename-board": "Preimenuj tablo", - "restore": "Obnovi", - "rescue-card-description": "Show rescue dialogue before closing for unsaved card descriptions", - "rescue-card-description-dialogue": "Overwrite current card description with your changes?", - "save": "Shrani", - "search": "Išči", - "rules": "Pravila", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Write text you search and press Enter", - "select-color": "Izberi barvo", - "select-board": "Select Board", - "set-wip-limit-value": "Omeji maksimalno število opravil v seznamu", - "setWipLimitPopup-title": "Omeji število kartic", - "shortcut-add-self": "Add yourself to current card", - "shortcut-assign-self": "Dodeli sebe k trenutni kartici", - "shortcut-autocomplete-emoji": "Samodokončaj emoji", - "shortcut-autocomplete-members": "Samodokončaj člane", - "shortcut-clear-filters": "Počisti vse filtre", - "shortcut-close-dialog": "Zapri dialog", - "shortcut-filter-my-cards": "Filtriraj moje kartice", - "shortcut-filter-my-assigned-cards": "Filter my assigned cards", - "shortcut-show-shortcuts": "Prikaži seznam bližnjic", - "shortcut-toggle-filterbar": "Toggle Filter Sidebar", - "shortcut-toggle-searchbar": "Toggle Search Sidebar", - "shortcut-toggle-sidebar": "Preklopi stransko vrstico table", - "show-cards-minimum-count": "Prikaži število kartic, če seznam vsebuje več kot", - "sidebar-open": "Odpri stransko vrstico", - "sidebar-close": "Zapri stransko vrstico", - "signupPopup-title": "Ustvari up. račun", - "star-board-title": "Označite tablo z zvezdico, da bo prikazana na vrhu v seznamu tabel.", - "starred-boards": "Table z zvezdico", - "starred-boards-description": "Table z zvezdico se prikažejo na vrhu vašega seznama tabel.", - "subscribe": "Naročite se", - "team": "Skupina", - "this-board": "tablo", - "this-card": "kartico", - "spent-time-hours": "Porabljen čas (ure)", - "overtime-hours": "Presežen čas (ure)", - "overtime": "Presežen čas", - "has-overtime-cards": "Ima kartice s preseženim časom", - "has-spenttime-cards": "Ima kartice s porabljenim časom", - "time": "Čas", - "title": "Naslov", - "toggle-assignees": "Toggle assignees 1-9 for card (By order of addition to board).", - "toggle-labels": "Toggle labels 1-9 for card. Multi-Selection adds labels 1-9", - "remove-labels-multiselect": "Multi-Selection removes labels 1-9", - "tracking": "Sledenje", - "tracking-info": "Obveščeni boste o vseh spremembah nad karticami, kjer ste lastnik ali član.", - "type": "Tip", - "unassign-member": "Odjavi člana", - "unsaved-description": "Imate neshranjen opis.", - "unwatch": "Prekliči opazovanje", - "upload": "Naloži", - "upload-avatar": "Naloži avatar", - "uploaded-avatar": "Naložil avatar", - "uploading-files": "Uploading files", - "upload-failed": "Upload failed", - "upload-completed": "Upload completed", - "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", - "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", - "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", - "custom-login-logo-image-url": "Custom Login Logo Image URL", - "custom-login-logo-link-url": "Custom Login Logo Link URL", - "custom-help-link-url": "Custom Help Link URL", - "text-below-custom-login-logo": "Text below Custom Login Logo", - "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", - "username": "Up. ime", - "import-usernames": "Import Usernames", - "view-it": "Poglej", - "warn-list-archived": "opozorilo: ta kartica je v seznamu v arhivu", - "watch": "Opazuj", - "watching": "Opazuje", - "watching-info": "O spremembah na tej tabli boste obveščeni", - "welcome-board": "Tabla Dobrodošli", - "welcome-swimlane": "Mejnik 1", - "welcome-list1": "Osnove", - "welcome-list2": "Napredno", - "card-templates-swimlane": "Predloge kartice", - "list-templates-swimlane": "Predloge seznama", - "board-templates-swimlane": "Predloge table", - "what-to-do": "Kaj želite storiti?", - "wipLimitErrorPopup-title": "Neveljaven limit št. kartic", - "wipLimitErrorPopup-dialog-pt1": "Število opravil v seznamu je višje od limita št. kartic.", - "wipLimitErrorPopup-dialog-pt2": "Prosimo premaknite nekaj opravil iz tega seznama ali nastavite višji limit št. kartic.", - "admin-panel": "Skrbniška plošča", - "settings": "Nastavitve", - "people": "Ljudje", - "registration": "Registracija", - "disable-self-registration": "Onemogoči samo-registracijo", - "disable-forgot-password": "Disable Forgot Password", - "invite": "Povabi", - "invite-people": "Povabi ljudi", - "to-boards": "K tabli(am)", - "email-addresses": "E-poštni naslovi", - "smtp-host-description": "Naslov vašega strežnika SMTP.", - "smtp-port-description": "Vrata vašega strežnika SMTP za odhodno pošto.", - "smtp-tls-description": "Omogoči šifriranje TLS za SMTP strežnik.", - "smtp-host": "SMTP Host", - "smtp-port": "SMTP vrata", - "smtp-username": "Up. ime", - "smtp-password": "Geslo", - "smtp-tls": "TLS podpora", - "send-from": "Od", - "send-smtp-test": "Pošljite testno e-pošto na svoj naslov", - "invitation-code": "Koda Povabila", - "email-invite-register-subject": "__inviter__ vam je poslal povabilo", - "email-invite-register-text": "Dragi __user__,\n\n__inviter__ vas vabi na kanban tablo za sodelovanje.\n\nProsimo sledite spodnji povezavi:\n__url__\n\nVaša koda povabila je: __icode__\n\nHvala.", - "email-smtp-test-subject": "SMTP testna e-pošta", - "email-smtp-test-text": "Uspešno ste poslali e-pošto", - "error-invitation-code-not-exist": "Koda povabila ne obstaja", - "error-notAuthorized": "Nimate pravic za ogled te strani.", - "webhook-title": "Ime spletnega vmesnika (webhook)", - "webhook-token": "Žeton (opcijsko za avtentikacijo)", - "outgoing-webhooks": "Izhodni spletni vmesniki (webhooks)", - "bidirectional-webhooks": "Dvo-smerni spletni vmesniki (webhooks)", - "outgoingWebhooksPopup-title": "Izhodni spletni vmesniki (webhooks)", - "boardCardTitlePopup-title": "Filter po naslovu kartice", - "disable-webhook": "Onemogoči ta spletni vmesnik (webhook)", - "global-webhook": "Globalni spletni vmesnik (webhook)", - "new-outgoing-webhook": "Nov izhodni spletni vmesnik (webhook)", - "no-name": "(Neznano)", - "Node_version": "Node različica", - "Meteor_version": "Meteor različica", - "MongoDB_version": "MongoDB različica", - "MongoDB_storage_engine": "MongoDB storage engine", - "MongoDB_Oplog_enabled": "MongoDB Oplog omogočen", - "OS_Arch": "OS Arhitektura", - "OS_Cpus": "OS število CPU", - "OS_Freemem": "OS prost pomnilnik", - "OS_Loadavg": "OS povp. obremenitev", - "OS_Platform": "OS platforma", - "OS_Release": "OS izdaja", - "OS_Totalmem": "OS skupni pomnilnik", - "OS_Type": "OS tip", - "OS_Uptime": "OS čas delovanja", - "days": "dnevi", - "hours": "ure", - "minutes": "minute", - "seconds": "sekunde", - "show-field-on-card": "Prikaži to polje na kartici", - "automatically-field-on-card": "Add field to new cards", - "always-field-on-card": "Add field to all cards", - "showLabel-field-on-card": "Prikaži oznako polja na mini kartici", - "showSum-field-on-list": "Show sum of fields at top of list", - "yes": "Da", - "no": "Ne", - "accounts": "Up. računi", - "accounts-allowEmailChange": "Dovoli spremembo e-poštnega naslova", - "accounts-allowUserNameChange": "Dovoli spremembo up. imena", - "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", - "createdAt": "Ustvarjen ob", - "modifiedAt": "Modified at", - "verified": "Preverjeno", - "active": "Aktivno", - "card-received": "Prejeto", - "card-received-on": "Prejeto ob", - "card-end": "Konec", - "card-end-on": "Končano na", - "editCardReceivedDatePopup-title": "Spremeni datum prejema", - "editCardEndDatePopup-title": "Spremeni končni datum", - "setCardColorPopup-title": "Nastavi barvo", - "setSelectionColorPopup-title": "Set selection color", - "setCardActionsColorPopup-title": "Izberi barvo", - "setSwimlaneColorPopup-title": "Izberi barvo", - "setListColorPopup-title": "Izberi barvo", - "assigned-by": "Dodelil", - "requested-by": "Zahteval", - "card-sorting-by-number": "Card sorting by number", - "board-delete-notice": "Brisanje je trajno. Izgubili boste vse sezname, kartice in akcije, povezane z desko.", - "delete-board-confirm-popup": "Vsi seznami, kartice, oznake in dejavnosti bodo izbrisani in vsebine table ne boste mogli obnoviti. Razveljavitve ni.", - "boardDeletePopup-title": "Izbriši tablo?", - "delete-board": "Izbriši tablo", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", - "delete-duplicate-lists": "Delete Duplicate Lists", - "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", - "default-subtasks-board": "Podopravila za tablo", - "default": "Privzeto", - "defaultdefault": "Privzeto", - "queue": "Čakalna vrsta", - "subtask-settings": "Nastavitve podopravil", - "card-settings": "Nastavitve kartice", - "minicard-settings": "Minicard Settings", - "boardSubtaskSettingsPopup-title": "Nastavitve podopravil", - "boardCardSettingsPopup-title": "Nastavitve kartice", - "boardMinicardSettingsPopup-title": "Minicard Settings", - "deposit-subtasks-board": "Deponiraj podopravila na tablo:", - "deposit-subtasks-list": "Ciljni seznam za deponirana podopravila:", - "show-parent-in-minicard": "Pokaži starša na mini-kartici:", - "description-on-minicard": "Description on minicard", - "cover-attachment-on-minicard": "Cover image on minicard", - "badge-attachment-on-minicard": "Count of attachments on minicard", - "card-sorting-by-number-on-minicard": "Card sorting by number on minicard", - "prefix-with-full-path": "Predpona s celotno potjo", - "prefix-with-parent": "Predpona s staršem", - "subtext-with-full-path": "Podbesedilo s celotno potjo", - "subtext-with-parent": "Podbesedilo s staršem", - "change-card-parent": "Zamenjaj starša kartice", - "parent-card": "Starševska kartica", - "source-board": "Izvorna tabla", - "no-parent": "Ne prikaži starša", - "activity-added-label": "dodal oznako '%s' do %s", - "activity-removed-label": "odstranil oznako '%s' od %s", - "activity-delete-attach": "izbrisal priponko od %s", - "activity-added-label-card": "dodal oznako '%s'", - "activity-removed-label-card": "izbrisal oznako '%s'", - "activity-delete-attach-card": "izbrisal priponko", - "activity-set-customfield": "nastavi polje po meri '%s' do '%s' v %s", - "activity-unset-customfield": "zbriši polje po meri '%s' v %s", - "r-rule": "Pravilo", - "r-add-trigger": "Dodaj prožilec", - "r-add-action": "Dodaj akcijo", - "r-board-rules": "Pravila table", - "r-add-rule": "Dodaj pravilo", - "r-view-rule": "Poglej pravilo", - "r-delete-rule": "Izbriši pravilo", - "r-new-rule-name": "Ime novega pravila", - "r-no-rules": "Ni pravil", - "r-trigger": "Trigger", - "r-action": "Action", - "r-when-a-card": "Ko je kartica", - "r-is": "is", - "r-is-moved": "premaknjena", - "r-added-to": "Added to", - "r-removed-from": "izbrisan iz", - "r-the-board": "tabla", - "r-list": "seznam", - "set-filter": "Nastavi filter", - "r-moved-to": "premaknjena v", - "r-moved-from": "premaknjena iz", - "r-archived": "premaknjena v arhiv", - "r-unarchived": "obnovljena iz arhiva", - "r-a-card": "kartico", - "r-when-a-label-is": "Ko je oznaka", - "r-when-the-label": "Ko je oznaka", - "r-list-name": "ime sezn.", - "r-when-a-member": "Ko je član", - "r-when-the-member": "Ko je član", - "r-name": "ime", - "r-when-a-attach": "Ko je priponka", - "r-when-a-checklist": "Ko je kontrolni seznam", - "r-when-the-checklist": "Ko kontrolni seznam", - "r-completed": "zaključen", - "r-made-incomplete": "nastavljen kot nedokončan", - "r-when-a-item": "Ko je kontrolni seznam", - "r-when-the-item": "Ko je element kontrolnega seznama", - "r-checked": "označen", - "r-unchecked": "odznačen", - "r-move-card-to": "Premakni kartico na", - "r-top-of": "Vrh", - "r-bottom-of": "Dno", - "r-its-list": "pripadajočega seznama", - "r-archive": "premaknjena v arhiv", - "r-unarchive": "Obnovi iz arhiva", - "r-card": "kartico", - "r-add": "Dodaj", - "r-remove": "Odstrani", - "r-label": "oznaka", - "r-member": "član", - "r-remove-all": "Izbriši vse člane iz kartice", - "r-set-color": "Nastavi barvo na", - "r-checklist": "kontrolni seznam", - "r-check-all": "Označi vse", - "r-uncheck-all": "Odznači vse", - "r-items-check": "postavke kontrolnega lista", - "r-check": "Označi", - "r-uncheck": "Odznači", - "r-item": "postavka", - "r-of-checklist": "kontrolnega seznama", - "r-send-email": "Pošlji e-pošto", - "r-to": "naslovnik", - "r-of": "of", - "r-subject": "zadeva", - "r-rule-details": "Podrobnosti pravila", - "r-d-move-to-top-gen": "Premakni kartico na vrh pripadajočega sezama", - "r-d-move-to-top-spec": "Premakni kartico na vrh seznama", - "r-d-move-to-bottom-gen": "Premakni kartico na dno pripadajočega seznama", - "r-d-move-to-bottom-spec": "Premakni kartico na dno seznama", - "r-d-send-email": "Pošlji e-pošto", - "r-d-send-email-to": "naslovnik", - "r-d-send-email-subject": "zadeva", - "r-d-send-email-message": "vsebina", - "r-d-archive": "Premakni kartico v arhiv", - "r-d-unarchive": "Obnovi kartico iz arhiva", - "r-d-add-label": "Dodaj oznako", - "r-d-remove-label": "Izbriši oznako", - "r-create-card": "Ustvari novo kartico", - "r-in-list": "v seznamu", - "r-in-swimlane": "v plavalni stezi", - "r-d-add-member": "Dodaj člana", - "r-d-remove-member": "Odstrani člana", - "r-d-remove-all-member": "Odstrani vse člane", - "r-d-check-all": "Označi vse elemente seznama", - "r-d-uncheck-all": "Odznači vse elemente seznama", - "r-d-check-one": "Označi element", - "r-d-uncheck-one": "Odznači element", - "r-d-check-of-list": "kontrolnega seznama", - "r-d-add-checklist": "Dodaj kontrolni list", - "r-d-remove-checklist": "Odstrani kotrolni list", - "r-by": "od", - "r-add-checklist": "Dodaj kontrolni list", - "r-with-items": "s postavkami", - "r-items-list": "el1,el2,el3", - "r-add-swimlane": "Dodaj plavalno stezo", - "r-swimlane-name": "ime pl. steze", - "r-board-note": "Note: leave a field empty to match every possible value. ", - "r-checklist-note": "Opomba: elementi kontrolnega seznama morajo biti zapisani kot vrednosti, ločene z vejicami.", - "r-when-a-card-is-moved": "Ko je kartica premaknjena v drug seznam", - "r-set": "Nastavi", - "r-update": "Posodobi", - "r-datefield": "polje z datumom", - "r-df-start-at": "začetek", - "r-df-due-at": "rok", - "r-df-end-at": "konec", - "r-df-received-at": "prejeto", - "r-to-current-datetime": "v trenutni datum/čas", - "r-remove-value-from": "Izbriši vrednost iz", - "r-link-card": "Link card to", - "ldap": "LDAP", - "oauth2": "OAuth2", - "cas": "CAS", - "authentication-method": "Metoda avtentikacije", - "authentication-type": "Način avtentikacije", - "custom-product-name": "Ime izdelka po meri", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", - "layout": "Postavitev", - "hide-logo": "Skrij logo", - "hide-card-counter-list": "Hide card counter list on All Boards", - "hide-board-member-list": "Hide board member list on All Boards", - "add-custom-html-after-body-start": "Dodaj HTML po meri po <body> začetku", - "add-custom-html-before-body-end": "Dodaj HMTL po meri po </body> koncu", - "error-undefined": "Prišlo je do napake", - "error-ldap-login": "Prišlo je do napake ob prijavi", - "display-authentication-method": "Prikaži metodo avtentikacije", - "oidc-button-text": "Customize the OIDC button text", - "default-authentication-method": "Privzeta metoda avtentikacije", - "duplicate-board": "Dupliciraj tablo", - "duplicate-board-confirm": "Are you sure you want to duplicate this board?", - "org-number": "The number of organizations is: ", - "team-number": "The number of teams is: ", - "people-number": "The number of people is: ", - "swimlaneDeletePopup-title": "Zbriši plavalno stezo?", - "swimlane-delete-pop": "Vsa dejanja bodo odstranjena iz seznama dejavnosti. Plavalne steze ne boste mogli obnoviti. Razveljavitve ni.", - "restore-all": "Obnovi vse", - "delete-all": "Izbriši vse", - "loading": "Nalagam, prosimo počakajte", - "previous_as": "zadnji čas je bil", - "act-a-dueAt": "spremenil rok zapadlosti na \nKdaj: __timeValue__\nKje: __card__\n prejšnji rok zapadlosti je bil __timeOldValue__", - "act-a-endAt": "spremenil čas dokončanja na __timeValue__ iz (__timeOldValue__)", - "act-a-startAt": "spremenil čas pričetka na __timeValue__ iz (__timeOldValue__)", - "act-a-receivedAt": "spremenil čas prejema na __timeValue__ iz (__timeOldValue__)", - "a-dueAt": "spremenil rok v", - "a-endAt": "spremenil končni čas v", - "a-startAt": "spremenil začetni čas v", - "a-receivedAt": "spremenil čas prejetja v", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", - "almostdue": "trenutni rok %s se približuje", - "pastdue": "trenutni rok %s je potekel", - "duenow": "trenutni rok %s je danes", - "act-newDue": "__list__/__card__ ima 1. opomnik roka zapadlosti [__board__]", - "act-withDue": "__list__/__card__ opomniki roka zapadlosti [__board__]", - "act-almostdue": "je opomnil trenuten rok zapadlosti (__timeValue__) kartice __card__ se bliža", - "act-pastdue": "je opomnil trenuten rok zapadlosti (__timeValue__) kartice __card__ je potekel", - "act-duenow": "je opomnil trenuten rok zapadlosti (__timeValue__) kartice __card__ je sedaj", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "delete-user-confirm-popup": "Ali ste prepričani, da želite izbrisati ta račun? Razveljavitve ni.", - "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", - "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", - "accounts-allowUserDelete": "Dovoli uporabnikom, da sami izbrišejo svoj račun", - "hide-minicard-label-text": "Skrij besedilo oznak na karticah", - "show-desktop-drag-handles": "Pokaži ročke za povleko na namizju", - "assignee": "Dodeljen član", - "cardAssigneesPopup-title": "Dodeljen član", - "addmore-detail": "Dodaj podrobnejši opis", - "show-on-card": "Prikaži na kartici", - "show-on-minicard": "Show on Minicard", - "new": "Novo", - "editOrgPopup-title": "Edit Organization", - "newOrgPopup-title": "New Organization", - "editTeamPopup-title": "Edit Team", - "newTeamPopup-title": "New Team", - "editUserPopup-title": "Uredi uporabnika", - "newUserPopup-title": "Nov uporabnik", - "notifications": "Notifications", - "help": "Help", - "view-all": "View All", - "filter-by-unread": "Filter by Unread", - "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", - "remove-all-read": "Remove all read", - "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename", - "start-day-of-week": "Set day of the week start", - "monday": "Monday", - "tuesday": "Tuesday", - "wednesday": "Wednesday", - "thursday": "Thursday", - "friday": "Friday", - "saturday": "Saturday", - "sunday": "Sunday", - "status": "Status", - "swimlane": "Swimlane", - "owner": "Owner", - "last-modified-at": "Last modified at", - "last-activity": "Last activity", - "voting": "Voting", - "archived": "Archived", - "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", - "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", - "hide-checked-items": "Hide checked items", - "hide-finished-checklist": "Hide finished checklist", - "task": "Task", - "create-task": "Create Task", - "ok": "OK", - "organizations": "Organizations", - "teams": "Teams", - "displayName": "Display Name", - "shortName": "Short Name", - "autoAddUsersWithDomainName": "Automatically add users with the domain name", - "website": "Website", - "person": "Person", - "my-cards": "My Cards", - "card": "Kartica", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", - "list": "List", - "board": "Board", - "context-separator": "/", - "myCardsViewChange-title": "My Cards View", - "myCardsViewChangePopup-title": "My Cards View", - "myCardsViewChange-choice-boards": "Table", - "myCardsViewChange-choice-table": "Table", - "myCardsSortChange-title": "My Cards Sort", - "myCardsSortChangePopup-title": "My Cards Sort", - "myCardsSortChange-choice-board": "By Board", - "myCardsSortChange-choice-dueat": "By Due Date", - "dueCards-title": "Due Cards", - "dueCardsViewChange-title": "Due Cards View", - "dueCardsViewChangePopup-title": "Due Cards View", - "dueCardsViewChange-choice-me": "Me", - "dueCardsViewChange-choice-all": "All Users", - "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", - "dueCards-noResults-title": "No Due Cards Found", - "dueCards-noResults-description": "You don't have any cards with due dates at the moment.", - "broken-cards": "Broken Cards", - "board-title-not-found": "Board '%s' not found.", - "swimlane-title-not-found": "Swimlane '%s' not found.", - "list-title-not-found": "List '%s' not found.", - "label-not-found": "Label '%s' not found.", - "label-color-not-found": "Label color %s not found.", - "user-username-not-found": "Username '%s' not found.", - "comment-not-found": "Card with comment containing text '%s' not found.", - "org-name-not-found": "Organization '%s' not found.", - "team-name-not-found": "Team '%s' not found.", - "globalSearch-title": "Search All Boards", - "no-cards-found": "No Cards Found", - "one-card-found": "One Card Found", - "n-cards-found": "%s Cards Found", - "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", - "operator-board": "board", - "operator-board-abbrev": "b", - "operator-swimlane": "swimlane", - "operator-swimlane-abbrev": "s", - "operator-list": "seznam", - "operator-list-abbrev": "l", - "operator-label": "oznaka", - "operator-label-abbrev": "#", - "operator-user": "user", - "operator-user-abbrev": "@", - "operator-member": "član", - "operator-member-abbrev": "m", - "operator-assignee": "assignee", - "operator-assignee-abbrev": "a", - "operator-creator": "creator", - "operator-status": "status", - "operator-due": "rok", - "operator-created": "created", - "operator-modified": "modified", - "operator-sort": "sort", - "operator-comment": "comment", - "operator-has": "has", - "operator-limit": "limit", - "operator-debug": "debug", - "operator-org": "org", - "operator-team": "team", - "predicate-archived": "archived", - "predicate-open": "open", - "predicate-ended": "ended", - "predicate-all": "all", - "predicate-overdue": "overdue", - "predicate-week": "week", - "predicate-month": "month", - "predicate-quarter": "quarter", - "predicate-year": "year", - "predicate-due": "rok", - "predicate-modified": "modified", - "predicate-created": "created", - "predicate-attachment": "attachment", - "predicate-description": "description", - "predicate-checklist": "kontrolni seznam", - "predicate-start": "začetek", - "predicate-end": "konec", - "predicate-assignee": "assignee", - "predicate-member": "član", - "predicate-public": "public", - "predicate-private": "private", - "predicate-selector": "selector", - "predicate-projection": "projection", - "operator-unknown-error": "%s is not an operator", - "operator-number-expected": "operator __operator__ expected a number, got '__value__'", - "operator-sort-invalid": "sort of '%s' is invalid", - "operator-status-invalid": "'%s' is not a valid status", - "operator-has-invalid": "%s is not a valid existence check", - "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", - "operator-debug-invalid": "%s is not a valid debug predicate", - "next-page": "Next Page", - "previous-page": "Previous Page", - "heading-notes": "Notes", - "globalSearch-instructions-heading": "Search Instructions", - "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", - "globalSearch-instructions-operators": "Available operators:", - "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", - "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", - "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", - "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", - "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", - "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", - "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", - "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", - "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", - "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", - "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", - "globalSearch-instructions-operator-org": "`__operator_org__:<display name|short name>` - cards belonging to a board assigned to organization *<name>*", - "globalSearch-instructions-operator-team": "`__operator_team__:<display name|short name>` - cards belonging to a board assigned to team *<name>*", - "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", - "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", - "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", - "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", - "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", - "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", - "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", - "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", - "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", - "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", - "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", - "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", - "globalSearch-instructions-notes-1": "Multiple operators may be specified.", - "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", - "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", - "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", - "globalSearch-instructions-notes-4": "Text searches are case insensitive.", - "globalSearch-instructions-notes-5": "By default archived cards are not searched.", - "link-to-search": "Link to this search", - "excel-font": "Arial", - "number": "Število", - "label-colors": "Label Colors", - "label-names": "Label Names", - "archived-at": "archived at", - "sort-cards": "Sort Cards", - "sort-is-on": "Sort is on", - "cardsSortPopup-title": "Sort Cards", - "due-date": "Due Date", - "server-error": "Server Error", - "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", - "title-alphabetically": "Title (Alphabetically)", - "created-at-newest-first": "Created At (Newest First)", - "created-at-oldest-first": "Created At (Oldest First)", - "links-heading": "Links", - "hide-activities-of-all-boards": "Don't show the board activities on all boards", - "now-activities-of-all-boards-are-hidden": "Now all activities of all boards are hidden", - "move-swimlane": "Move Swimlane", - "moveSwimlanePopup-title": "Move Swimlane", - "custom-field-stringtemplate": "String Template", - "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", - "custom-field-stringtemplate-separator": "Separator (use or   for a space)", - "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", - "creator": "Creator", - "creator-on-minicard": "Creator on minicard", - "filesReportTitle": "Files Report", - "reports": "Reports", - "rulesReportTitle": "Rules Report", - "boardsReportTitle": "Boards Report", - "cardsReportTitle": "Cards Report", - "copy-swimlane": "Copy Swimlane", - "copySwimlanePopup-title": "Copy Swimlane", - "display-card-creator": "Display Card Creator", - "wait-spinner": "Wait Spinner", - "Bounce": "Bounce Wait Spinner", - "Cube": "Cube Wait Spinner", - "Cube-Grid": "Cube-Grid Wait Spinner", - "Dot": "Dot Wait Spinner", - "Double-Bounce": "Double Bounce Wait Spinner", - "Rotateplane": "Rotateplane Wait Spinner", - "Scaleout": "Scaleout Wait Spinner", - "Wave": "Wave Wait Spinner", - "maximize-card": "Maximize Card", - "minimize-card": "Minimize Card", - "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", - "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it", - "subject": "Subject", - "details": "Details", - "carbon-copy": "Carbon Copy (Cc:)", - "ticket": "Ticket", - "tickets": "Tickets", - "ticket-number": "Ticket Number", - "open": "Open", - "pending": "Pending", - "closed": "Closed", - "resolved": "Resolved", - "cancelled": "Cancelled", - "history": "History", - "request": "Request", - "requests": "Requests", - "help-request": "Help Request", - "editCardSortOrderPopup-title": "Change Sorting", - "cardDetailsPopup-title": "Card Details", - "add-teams": "Add teams", - "add-teams-label": "Added teams are displayed below:", - "remove-team-from-table": "Are you sure you want to remove this team from the board ?", - "confirm-btn": "Confirm", - "remove-btn": "Odstrani", - "filter-card-title-label": "Filter by card title", - "invite-people-success": "Invitation to register sent with success", - "invite-people-error": "Error while sending invitation to register", - "can-invite-if-same-mailDomainName": "Email domain name", - "to-create-teams-contact-admin": "To create teams, please contact the administrator.", - "Node_heap_total_heap_size": "Node heap: total heap size", - "Node_heap_total_heap_size_executable": "Node heap: total heap size executable", - "Node_heap_total_physical_size": "Node heap: total physical size", - "Node_heap_total_available_size": "Node heap: total available size", - "Node_heap_used_heap_size": "Node heap: used heap size", - "Node_heap_heap_size_limit": "Node heap: heap size limit", - "Node_heap_malloced_memory": "Node heap: malloced memory", - "Node_heap_peak_malloced_memory": "Node heap: peak malloced memory", - "Node_heap_does_zap_garbage": "Node heap: does zap garbage", - "Node_heap_number_of_native_contexts": "Node heap: number of native contexts", - "Node_heap_number_of_detached_contexts": "Node heap: number of detached contexts", - "Node_memory_usage_rss": "Node memory usage: resident set size", - "Node_memory_usage_heap_total": "Node memory usage: total size of the allocated heap", - "Node_memory_usage_heap_used": "Node memory usage: actual memory used", - "Node_memory_usage_external": "Node memory usage: external", - "add-organizations": "Add organizations", - "add-organizations-label": "Added organizations are displayed below:", - "remove-organization-from-board": "Are you sure you want to remove this organization from this board ?", - "to-create-organizations-contact-admin": "To create organizations, please contact administrator.", - "custom-legal-notice-link-url": "Custom legal notice page URL", - "acceptance_of_our_legalNotice": "By continuing, you accept our", - "legalNotice": "legal notice", - "copied": "Copied!", - "checklistActionsPopup-title": "Checklist Actions", - "moveChecklist": "Move Checklist", - "moveChecklistPopup-title": "Move Checklist", - "newlineBecomesNewChecklistItem": "Each line of text becomes one of the checklist items", - "newLineNewItem": "One line of text = one checklist item", - "newlineBecomesNewChecklistItemOriginOrder": "Each line of text becomes one of the checklist items, original order", - "originOrder": "original order", - "copyChecklist": "Copy Checklist", - "copyChecklistPopup-title": "Copy Checklist", - "card-show-lists": "Card Show Lists", - "subtaskActionsPopup-title": "Subtask Actions", - "attachmentActionsPopup-title": "Attachment Actions", - "attachment-move-storage-fs": "Move attachment to filesystem", - "attachment-move-storage-gridfs": "Move attachment to GridFS", - "attachment-move-storage-s3": "Move attachment to S3", - "attachment-move": "Move Attachment", - "move-all-attachments-to-fs": "Move all attachments to filesystem", - "move-all-attachments-to-gridfs": "Move all attachments to GridFS", - "move-all-attachments-to-s3": "Move all attachments to S3", - "move-all-attachments-of-board-to-fs": "Move all attachments of board to filesystem", - "move-all-attachments-of-board-to-gridfs": "Move all attachments of board to GridFS", - "move-all-attachments-of-board-to-s3": "Move all attachments of board to S3", - "path": "Path", - "version-name": "Version-Name", - "size": "Size", - "storage": "Storage", - "action": "Action", - "board-title": "Board Title", - "attachmentRenamePopup-title": "Preimenuj", - "uploading": "Uploading", - "remaining_time": "Remaining time", - "speed": "Speed", - "progress": "Progress", - "password-again": "Password (again)", - "if-you-already-have-an-account": "If you already have an account", - "register": "Register", - "forgot-password": "Forgot password", - "minicardDetailsActionsPopup-title": "Card Details", - "Mongo_sessions_count": "Mongo sessions count", - "change-visibility": "Spremeni vidnost", - "max-upload-filesize": "Max upload filesize in bytes:", - "allowed-upload-filetypes": "Allowed upload filetypes:", - "max-avatar-filesize": "Max avatar filesize in bytes:", - "allowed-avatar-filetypes": "Allowed avatar filetypes:", - "invalid-file": "If filename is invalid, upload or rename is cancelled.", - "preview-pdf-not-supported": "Your device does not support previewing PDF. Try downloading instead.", - "drag-board": "Drag board", - "translation-number": "The number of custom translation strings is:", - "delete-translation-confirm-popup": "Are you sure you want to delete this custom translation string? There is no undo.", - "newTranslationPopup-title": "New custom translation string", - "editTranslationPopup-title": "Edit custom translation string", - "settingsTranslationPopup-title": "Delete this custom translation string?", - "translation": "Translation", - "text": "Besedilo", - "translation-text": "Translation text", - "show-subtasks-field": "Show subtasks field", - "show-week-of-year": "Show week of year (ISO 8601)", - "convert-to-markdown": "Convert to markdown", - "import-board-zip": "Add .zip file that has board JSON files, and board name subdirectories with attachments", - "collapse": "Skrči", - "uncollapse": "Uncollapse", - "hideCheckedChecklistItems": "Hide checked checklist items", - "hideAllChecklistItems": "Hide all checklist items", - "support": "Support", - "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", - "accessibility": "Dostopnost", - "accessibility-page-enabled": "Accessibility page enabled", - "accessibility-info-not-added-yet": "Accessibility info has not been added yet", - "accessibility-title": "Accessibility title", - "accessibility-content": "Accessibility content", - "accounts-lockout-settings": "Brute Force Protection Settings", - "accounts-lockout-info": "These settings control how login attempts are protected against brute force attacks.", - "accounts-lockout-known-users": "Settings for known users (correct username, wrong password)", - "accounts-lockout-unknown-users": "Settings for unknown users (non-existent username)", - "accounts-lockout-failures-before": "Failures before lockout", - "accounts-lockout-period": "Lockout period (seconds)", - "accounts-lockout-failure-window": "Failure window (seconds)", - "accounts-lockout-settings-updated": "Brute force protection settings have been updated", - "accounts-lockout-locked-users": "Locked Users", - "accounts-lockout-locked-users-info": "Users currently locked out due to too many failed login attempts", - "accounts-lockout-no-locked-users": "There are currently no locked users", - "accounts-lockout-failed-attempts": "Failed Attempts", - "accounts-lockout-remaining-time": "Remaining Time", - "accounts-lockout-user-unlocked": "User has been unlocked successfully", - "accounts-lockout-confirm-unlock": "Are you sure you want to unlock this user?", - "accounts-lockout-confirm-unlock-all": "Are you sure you want to unlock all locked users?", - "accounts-lockout-show-locked-users": "Show locked users only", - "accounts-lockout-user-locked": "User is locked", - "accounts-lockout-click-to-unlock": "Click to unlock this user", - "accounts-lockout-status": "Status", - "admin-people-filter-show": "Show:", - "admin-people-filter-all": "All Users", - "admin-people-filter-locked": "Locked Users Only", - "admin-people-filter-active": "Aktivno", - "admin-people-filter-inactive": "Not Active", - "admin-people-active-status": "Active Status", - "admin-people-user-active": "User is active - click to deactivate", - "admin-people-user-inactive": "User is inactive - click to activate", - "accounts-lockout-all-users-unlocked": "All locked users have been unlocked", - "accounts-lockout-unlock-all": "Unlock All", - "active-cron-jobs": "Active Scheduled Jobs", - "add-cron-job": "Add Scheduled Job", - "add-cron-job-placeholder": "Add Scheduled Job functionality coming soon", - "attachment-storage-configuration": "Attachment Storage Configuration", - "attachments-path": "Attachments Path", - "attachments-path-description": "Path where attachment files are stored", - "avatars-path": "Avatars Path", - "avatars-path-description": "Path where avatar files are stored", - "board-archive-failed": "Failed to schedule board archive", - "board-archive-scheduled": "Board archive scheduled successfully", - "board-backup-failed": "Failed to schedule board backup", - "board-backup-scheduled": "Board backup scheduled successfully", - "board-cleanup-failed": "Failed to schedule board cleanup", - "board-cleanup-scheduled": "Board cleanup scheduled successfully", - "board-operations": "Board Operations", - "cron-jobs": "Scheduled Jobs", - "cron-migrations": "Scheduled Migrations", - "cron-job-delete-confirm": "Are you sure you want to delete this scheduled job?", - "cron-job-delete-failed": "Failed to delete scheduled job", - "cron-job-deleted": "Scheduled job deleted successfully", - "cron-job-pause-failed": "Failed to pause scheduled job", - "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Čas", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", - "filesystem-path-description": "Base path for file storage", - "gridfs-enabled": "GridFS Enabled", - "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Začetek", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", - "migration-pause-failed": "Failed to pause migrations", - "migration-paused": "Migrations paused successfully", - "migration-progress": "Migration Progress", - "migration-start-failed": "Failed to start migrations", - "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", - "migration-status": "Migration Status", - "migration-stop-confirm": "Are you sure you want to stop all migrations?", - "migration-stop-failed": "Failed to stop migrations", - "migration-stopped": "Migrations stopped successfully", - "mongodb-gridfs-storage": "MongoDB GridFS Storage", - "pause-all-migrations": "Pause All Migrations", - "s3-access-key": "S3 Access Key", - "s3-access-key-description": "AWS S3 access key for authentication", - "s3-access-key-placeholder": "Enter S3 access key", - "s3-bucket": "S3 Bucket", - "s3-bucket-description": "S3 bucket name for storing files", - "s3-connection-failed": "S3 connection failed", - "s3-connection-success": "S3 connection successful", - "s3-enabled": "S3 Enabled", - "s3-enabled-description": "Use AWS S3 or MinIO for file storage", - "s3-endpoint": "S3 Endpoint", - "s3-endpoint-description": "S3 endpoint URL (e.g., s3.amazonaws.com or minio.example.com)", - "s3-minio-storage": "S3/MinIO Storage", - "s3-port": "S3 Port", - "s3-port-description": "S3 endpoint port number", - "s3-region": "S3 Region", - "s3-region-description": "AWS S3 region (e.g., us-east-1)", - "s3-secret-key": "S3 Secret Key", - "s3-secret-key-description": "AWS S3 secret key for authentication", - "s3-secret-key-placeholder": "Enter S3 secret key", - "s3-secret-key-required": "S3 secret key is required", - "s3-settings-save-failed": "Failed to save S3 settings", - "s3-settings-saved": "S3 settings saved successfully", - "s3-ssl-enabled": "S3 SSL Enabled", - "s3-ssl-enabled-description": "Use SSL/TLS for S3 connections", - "save-s3-settings": "Save S3 Settings", - "schedule-board-archive": "Schedule Board Archive", - "schedule-board-backup": "Schedule Board Backup", - "schedule-board-cleanup": "Schedule Board Cleanup", - "scheduled-board-operations": "Scheduled Board Operations", - "start-all-migrations": "Start All Migrations", - "stop-all-migrations": "Stop All Migrations", - "test-s3-connection": "Test S3 Connection", - "writable-path": "Writable Path", - "writable-path-description": "Base directory path for file storage", - "add-job": "Add Job", - "attachment-migration": "Attachment Migration", - "attachment-monitoring": "Attachment Monitoring", - "attachment-settings": "Attachment Settings", - "attachment-storage-settings": "Storage Settings", - "automatic-migration": "Automatic Migration", - "back-to-settings": "Back to Settings", - "board-id": "Board ID", - "board-migration": "Board Migration", - "board-migrations": "Board Migrations", - "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", - "cleanup": "Cleanup", - "cleanup-old-jobs": "Cleanup Old Jobs", - "completed": "zaključen", - "conversion-info-text": "This conversion is performed once per board and improves performance. You can continue using the board normally.", - "converting-board": "Converting Board", - "converting-board-description": "Converting board structure for improved functionality. This may take a few moments.", - "cpu-cores": "CPU Cores", - "cpu-usage": "CPU Usage", - "current-action": "Current Action", - "database-migration": "Database Migration", - "database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.", - "database-migrations": "Database Migrations", - "days-old": "Days Old", - "duration": "Duration", - "errors": "Errors", - "estimated-time-remaining": "Estimated time remaining", - "every-1-day": "Every 1 day", - "every-1-hour": "Every 1 hour", - "every-1-minute": "Every 1 minute", - "every-10-minutes": "Every 10 minutes", - "every-30-minutes": "Every 30 minutes", - "every-5-minutes": "Every 5 minutes", - "every-6-hours": "Every 6 hours", - "export-monitoring": "Export Monitoring", - "filesystem-attachments": "Filesystem Attachments", - "filesystem-size": "Filesystem Size", - "filesystem-storage": "Filesystem Storage", - "force-board-scan": "Force Board Scan", - "gridfs-attachments": "GridFS Attachments", - "gridfs-size": "GridFS Size", - "gridfs-storage": "GridFS", - "hide-list-on-minicard": "Hide List on Minicard", - "idle-migration": "Idle Migration", - "job-description": "Job Description", - "job-details": "Job Details", - "job-name": "Job Name", - "job-queue": "Job Queue", - "last-run": "Last Run", - "max-concurrent": "Max Concurrent", - "memory-usage": "Memory Usage", - "migrate-all-to-filesystem": "Migrate All to Filesystem", - "migrate-all-to-gridfs": "Migrate All to GridFS", - "migrate-all-to-s3": "Migrate All to S3", - "migrated-attachments": "Migrated Attachments", - "migration-batch-size": "Batch Size", - "migration-batch-size-description": "Number of attachments to process in each batch (1-100)", - "migration-cpu-threshold": "CPU Threshold (%)", - "migration-cpu-threshold-description": "Pause migration when CPU usage exceeds this percentage (10-90)", - "migration-delay-ms": "Delay (ms)", - "migration-delay-ms-description": "Delay between batches in milliseconds (100-10000)", - "migration-detector": "Migration Detector", - "migration-info-text": "Database migrations are performed once and improve system performance. The process continues in the background even if you close your browser.", - "migration-log": "Migration Log", - "migration-markers": "Migration Markers", - "migration-resume-failed": "Failed to resume migration", - "migration-resumed": "Migration resumed", - "migration-steps": "Migration Steps", - "migration-warning-text": "Please do not close your browser during migration. The process will continue in the background but may take longer to complete.", - "monitoring-export-failed": "Failed to export monitoring data", - "monitoring-refresh-failed": "Failed to refresh monitoring data", - "next": "Next", - "next-run": "Next Run", - "of": "of", - "operation-type": "Operation Type", - "overall-progress": "Overall Progress", - "page": "Page", - "pause-migration": "Pause Migration", - "previous": "Previous", - "refresh": "Refresh", - "refresh-monitoring": "Refresh Monitoring", - "remaining-attachments": "Remaining Attachments", - "resume-migration": "Resume Migration", - "run-once": "Run once", - "s3-attachments": "S3 Attachments", - "s3-size": "S3 Size", - "s3-storage": "S3", - "scanning-status": "Scanning Status", - "schedule": "Schedule", - "search-boards-or-operations": "Search boards or operations...", - "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", - "showing": "Showing", - "start-test-operation": "Start Test Operation", - "start-time": "Start Time", - "step-progress": "Step Progress", - "stop-migration": "Stop Migration", - "storage-distribution": "Storage Distribution", - "system-resources": "System Resources", - "total-attachments": "Total Attachments", - "total-operations": "Total Operations", - "total-size": "Total Size", - "unmigrated-boards": "Unmigrated Boards", - "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" -} diff --git a/imports/i18n/data/sr.i18n.json b/imports/i18n/data/sr.i18n.json index 2617d88a4..2022488c1 100644 --- a/imports/i18n/data/sr.i18n.json +++ b/imports/i18n/data/sr.i18n.json @@ -1,254 +1,237 @@ { - "accept": "Прихватам", + "accept": "Прихвати", "act-activity-notify": "Обавештење о последњим променама", - "act-addAttachment": "унео предметну грађу __attachment__ у предмет __card__ у делу __list__ поступка __swimlane__ међу списе __board__", - "act-deleteAttachment": "уклонио предметну грађу __attachment__ из предмета __card__ у делу __list__ поступка __swimlane__ из списа __board__", - "act-addSubtask": "је издвојио посао __subtask__ из предмета __card__ у __list__ делу __swimlane__ поступка у списима __board__", - "act-addLabel": "Залепио налепницу __label__ на омот предмета __card__ у делу __list__ поступка __swimlane__ у списима __board__", - "act-addedLabel": "Залепљена је налепница __label__ на омот предмета __card__ у делу __list__ поступка __swimlane__ у списима __board__", - "act-removeLabel": "Скинуо је налепницу __label__ са омота предмета __card__ у делу __list__ поступка __swimlane__ из списа __board__", - "act-removedLabel": "Скинута је налепница __label__ са омота предмета __card__ у делу __list__ поступка __swimlane__ из списа __board__", - "act-addChecklist": "је додао списак радњи __checklist__ у предмет __card__ у __list__ делу поступка __swimlane__ заведеног у списима __board__", - "act-addChecklistItem": "је додао ставку __checklistItem__ на списак __checklist__ у предмету __card__ у __list__ поступка __swimlane__ у списима __board__", - "act-removeChecklist": "је уклонио списак радњи __checklist__ из предмета __card__ у __list__ делу поступка __swimlane__ у списима __board__", - "act-removeChecklistItem": "је уклонио ставку __checklistItem__ са списка __checkList__ предмета __card__ у __list__ делу __swimlane__ у списима __board__", - "act-checkedItem": "је прецртао ставку __checklistItem__ са списка __checklist__ у предмету __card__ у __list__ делу __swimlane__ у списима __board__", - "act-uncheckedItem": "је вратио да се поново обави __checklistItem__ радња са списка __checklist__ у предмету __card__ у __list__ делу __swimlane__ у списима __board__", - "act-completeChecklist": "је испунио све са списка __checklist__ у предмету __card__ у __list__ делу __swimlane__ у списима __board__", - "act-uncompleteChecklist": "није испунио све са списка __checklist__ у предмету __card__ у __list__ делу __swimlane__ у списима __board__", - "act-addComment": "изразио следеће мишљење у расправи у предмету __card__: __comment__ у делу __list__ поступка __swimlane__ у списима __board__", - "act-editComment": "допунио став у расправи у предмету __card__:__comment__ у делу __list__ поступка __swimlane__ у списима __board__", - "act-deleteComment": "повукао мишљење у предмету __card__:__comment__ у делу __list__ поступка __swimlane__ у списима __board__", - "act-createBoard": "је увео нове списе са деловодним бројем __board__", - "act-createSwimlane": "је додао нови предметни ток __swimlane__ међу списе __board__", - "act-createCard": "је додао нови предмет __card__ у делу __list__ поступка __swimlane__ међу списе __board__", - "act-createCustomField": "је додао нову рубрику __customField__ међу списе __board__", - "act-deleteCustomField": "је избрисао рубрику __customField__ из списа __board__", - "act-setCustomField": "је изменио запис у рубрици __customField__:__customFieldValue__ у предмету __card__ у __list__ делу поступка __swimlane__ у списима __board", - "act-createList": "је придодао део поступка __list__ међу списе __board__", - "act-addBoardMember": "примљен сарадник __member__ за рад на списима __board__", - "act-archivedBoard": "Списи __board__ су спаковани у архив", - "act-archivedCard": "Предмет __card__ из дела __list__ поступка __swimlane__ из списа __board__ је спакован у архив", - "act-archivedList": "Део __list__ поступка __swimlane__ из списа __board__ је спакован у архив", - "act-archivedSwimlane": "Врста поступка __swimlane__ из списа __board__ је спакована у архив", - "act-importBoard": "унео списе __board__", - "act-importCard": "унео предмет __card__ у делу __list__ поступка __swimlane__ у списе __board__", - "act-importList": "унео део __list__ поступка __swimlane__ у списе __board__", - "act-joinMember": "примљен сарадник __member__ да ради на предмету __card__ у делу __list__ поступка __swimlane__ у списима __board__", - "act-moveCard": "преместио предмет __card__ из списа __board__ са старог дела __oldList__ поступка __oldSwimlane__ у нови део __list__ поступка __swimlane__", - "act-moveCardToOtherBoard": "преместио предмет __card__ са старог дела __oldList__ поступка __oldSwimlane__ из старих списа __oldBoard__ у део __list__ поступка __swimlane__ међу списе __board__", - "act-removeBoardMember": "изузео сарадника __member__ из списа __board__", - "act-restoredCard": "опоравио предмет __card__ у делу поступка __list__ из поступка __swimlane__ из списа __board__", - "act-unjoinMember": "изузео сарадника __member__ са предмета __card__ у делу поступка __list__ из поступка __swimlane__ из списа __board__", + "act-addAttachment": "додао прилог __attachment__ на картици __card__ на деоници __list__ на стази __swimlane__ у пословној књизи __board__", + "act-deleteAttachment": "уклонио прилог __attachment__ са картице __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-addSubtask": "додао подзадатак __subtask__ на картицу __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-addLabel": "Залепио налепницу __label__ на картицу __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-addedLabel": "Залепљена је налепница __label__ на картицу __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-removeLabel": "Скинуо налепницу __label__ са картице __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-removedLabel": "Скинута је налепница __label__ са картице __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-addChecklist": "додао списак за обавити __checklist__ на задатак __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-addChecklistItem": "додао ставку __checklistItem__ на списак за обавити __checklist__ задатка __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-removeChecklist": "уклонио списак за обавити __checklist__ са задатка __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-removeChecklistItem": "уклонио ставку __checklistItem__ са списка за обавити __checkList__ задатка __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-checkedItem": "обавио __checklistItem__ са списка __checklist__ на задатку __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-uncheckedItem": "вратио да се поново обави __checklistItem__ са списка __checklist__ на задатку __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-completeChecklist": "обавио све са списка __checklist__ на задатку __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-uncompleteChecklist": "није обављено све са списка __checklist__ на задатку __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-addComment": "изразио мишљење на картици __card__: __comment__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-editComment": "изменио мишљење на картици __card__:__comment__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-deleteComment": "повукао мишљење на картици __card__:__comment__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-createBoard": "Отворена је сасвим нова пословна књига __board__", + "act-createSwimlane": "Додата је сасвим нова стаза __swimlane__ у пословној књизи __board__", + "act-createCard": "додата је сасвим нова картица са задатком __card__ на деоници __list__ на стази __swimlane__ у пословној књизи __board__", + "act-createCustomField": "направљено сасвим ново поље __customField__ у пословној књизи __board__", + "act-deleteCustomField": "избрисано сасвим ново поље __customField__ у пословној књизи __board__", + "act-setCustomField": "измењено сасвим ново поље __customField__:__customFieldValue__ на картици са задатком __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board", + "act-createList": "придодата деоница __list__ у пословној књизи __board__", + "act-addBoardMember": "примљен сарадник __member__ у пословној књизи __board__", + "act-archivedBoard": "Пословна књига __board__ је премештена у архив", + "act-archivedCard": "Картица са задатком __card__ са деонице __list__ стазе __swimlane__ из пословне књиге __board__ однешена у архив", + "act-archivedList": "Деоница __list__ стазе __swimlane__ из пословне књиге __board__ премештена у архив", + "act-archivedSwimlane": "Стаза __swimlane__ из пословне књиге __board__ однешена у архив", + "act-importBoard": "увезао књигу пословања __board__", + "act-importCard": "увезао картицу са задатком __card__ на деоници __list__ на стази __swimlane__ у пословној књизи __board__", + "act-importList": "увезао деоницу __list__ на стазу __swimlane__ у пословној књизи __board__", + "act-joinMember": "примио сарадника __member__ на картицу __card__ на деоници __list__ стазе __swimlane__ у пословној књизи __board__", + "act-moveCard": "преместио картицу __card__ из пословне књиге __board__ са старе деонице __oldList__ старе стазе __oldSwimlane__ на деоницу __list__ нове стазе __swimlane__", + "act-moveCardToOtherBoard": "преместио картицу __card__ са старе деонице __oldList__ старе стазе __oldSwimlane__ из старе пословне књиге __oldBoard__ на деоницу __list__ стазе __swimlane__ у пословну књигу __board__", + "act-removeBoardMember": "удаљио сарадника __member__ из пословне књиге __board__", + "act-restoredCard": "обновио картицу са задатком __card__ на деоници __list__ у стази __swimlane__ у пословној књизи __board__", + "act-unjoinMember": "удаљио сарадника __member__ са картице __card__ на деоници __list__ стазе __swimlane__ из пословне књиге __board__", "act-withBoardTitle": "__board__", "act-withCardTitle": "[__board__] __card__", "actions": "Радње", - "activities": "Дневник промена", + "activities": "Последње промене", "activity": "Последња промена", - "activity-added": "је додао %s у %s", + "activity-added": "додао %s у %s", "activity-archived": "%s премештено у архиву", - "activity-attached": "унео предметну грађу %s у %s", - "activity-created": "је направио %s", - "activity-changedListTitle": "је преименовао део поступка у %s", - "activity-customfield-created": "је додао рубрику %s", + "activity-attached": "приложио %s у %s", + "activity-created": "направио %s", + "activity-changedListTitle": "renamed list to %s", + "activity-customfield-created": "направио сасвим ново поље %s", "activity-excluded": "изузет %s из %s", "activity-imported": "увезао %s у %s из %s", "activity-imported-board": "увезао %s из %s", "activity-joined": "спојио %s", - "activity-moved": "је преместио %s из %s у %s", + "activity-moved": "преместио %s из %s у %s", "activity-on": "на %s", "activity-removed": "уклонио %s из %s", "activity-sent": "послао %s %s-у", "activity-unjoined": "раставио %s", - "activity-subtask-added": "је издвојио посао из предмета %s", - "activity-checked-item": "пријављује да је обавио задатак %s са списка %s у предмету %s", - "activity-unchecked-item": "повлачи пријаву да је обавио задатак %s са списка %s у предмету %s", - "activity-checklist-added": "је унео предметну радњу у предмет %s", - "activity-checklist-removed": "је уклонио предметну радњу из предмета %s", - "activity-checklist-completed": "је испунио све са списка предметне радње %s у предмету %s", - "activity-checklist-uncompleted": "ипак није испунио све са списка предметне радње %s у предмету %s", - "activity-checklist-item-added": "је придодао ставку '%s' у предметну радњу %s", - "activity-checklist-item-removed": "је ставку '%s' избацио из предметне радње %s", + "activity-subtask-added": "додао подзадатак за %s", + "activity-checked-item": "обављено %s са списка %s од %s", + "activity-unchecked-item": "није обављено %s са списка %s од %s", + "activity-checklist-added": "деоница је додата у %s", + "activity-checklist-removed": "уклонио списак за обавити са %s", + "activity-checklist-completed": "испуњеност задатака са списка %s од %s", + "activity-checklist-uncompleted": "списак није испуњен %s од %s", + "activity-checklist-item-added": "придодата ставка на списак за обавити '%s' у %s", + "activity-checklist-item-removed": "избачена ставка са списка за обавити из '%s' у %s", "add": "Додај", - "activity-checked-item-card": "је испунио обавезу %s као ставку предметне радње %s", - "activity-unchecked-item-card": "је означио обавезу %s ипак као неиспуњен део предметне радње %s", - "activity-checklist-completed-card": "испуњене обавезе са списка __checklist__ у оквиру предмета __card__ у __list__ делу поступка __swimlane__ заведеног у списима __board__", + "activity-checked-item-card": "испуњена обавеза %s у списку за обавити %s", + "activity-unchecked-item-card": "неиспуњена обавеза %s на списку за обавити %s", + "activity-checklist-completed-card": "испуњене обавезе са списка __checklist__ на картици __card__ деонице __list__ на стази __swimlane__ пословне књиге __board__", "activity-checklist-uncompleted-card": "нису испуњене обавезе са списка %s", "activity-editComment": "променио мишљење", "activity-deleteComment": "повучено мишљење", - "activity-receivedDate": "је изменио датум и време пријема на %s у предмету %s", - "activity-startDate": "је изменио датум и време %s кад почиње да ради на предмету %s", - "allboards.starred": "Истакнути", - "allboards.templates": "Обрасци", - "allboards.remaining": "Нерасподељени", - "allboards.workspaces": "Радни простор", - "allboards.add-workspace": "Прошири радни простор", - "allboards.add-workspace-prompt": "Назив радног простора", - "allboards.add-subworkspace": "Додај радни кутак", - "allboards.add-subworkspace-prompt": "Назив за радни кутак", - "allboards.edit-workspace": "Преименовање радног простора", - "allboards.edit-workspace-name": "Назив радног простора", - "allboards.edit-workspace-icon": "Слика за радни простор (код)", - "multi-selection-active": "Штиклирајте да би изабрали списе", - "activity-dueDate": "је изменио крајњи рок на %s у предмету %s", - "activity-endDate": "је изменио датум и време %s кад је окончао предмет %s", - "add-attachment": "Додај предметну грађу", - "add-board": "Уведи нове списе", - "add-template": "Додај образац", - "add-card": "Додај предмет", - "add-card-to-top-of-list": "Додај предмет на врх", - "add-card-to-bottom-of-list": "Додај предмет на дно", - "addListPopup-title": "Додај деоницу", - "setListWidthPopup-title": "Ширина дела поступка", - "set-list-width": "Постави ширину", - "set-list-width-value": "Мин/Макс. ширина (px)", - "list-width-error-message": "Ширина дела поступка мора бити цео број већи од 100", - "keyboard-shortcuts-enabled": "Пречице са тастатуре су укључене.", - "keyboard-shortcuts-disabled": "Пречице са тастатуре су искључене.", - "setSwimlaneHeightPopup-title": "Подеси висину у овој врсти поступка", - "set-swimlane-height": "Подеси висину овог поступка", - "set-swimlane-height-value": "Висина ове врсте поступка (у тачкама)", - "swimlane-height-error-message": "Висина тока поступка мора бити позитиван број", - "add-swimlane": "Додај врсту поступка", - "add-subtask": "Издвоји посао", - "add-checklist": "Додај предметну радњу", - "add-checklist-item": "Додај помоћну предметну радњу", - "close-add-checklist-item": "Окончај додавање помоћних предметних радњи", - "close-edit-checklist-item": "Прекини измену помоћне предметне радње", - "convertChecklistItemToCardPopup-title": "Материјал ⇨ нов предмет", - "add-cover": "Осликај омот предмета", + "activity-receivedDate": "измењен датум пријема на %s са %s", + "activity-startDate": "измењен почетни датум на %s са %s", + "activity-dueDate": "измењен крајњи рок на %s са %s", + "activity-endDate": "измењено време завршетка на %s са %s", + "add-attachment": "Додај прилог", + "add-board": "Уведи пословну књигу", + "add-template": "Додај предложак", + "add-card": "Додај картицу са задатком", + "add-card-to-top-of-list": "Додај картицу/задатак на врх деонице", + "add-card-to-bottom-of-list": "Додај картицу/задатак на дно деонице", + "setListWidthPopup-title": "Set Widths", + "set-list-width": "Set Widths", + "set-list-width-value": "Set Min & Max Widths (pixels)", + "list-width-error-message": "List widths must be integers greater than 100", + "keyboard-shortcuts-enabled": "Keyboard shortcuts enabled. Click to disable.", + "keyboard-shortcuts-disabled": "Keyboard shortcuts disabled. Click to enable.", + "setSwimlaneHeightPopup-title": "Подеси висину стазе", + "set-swimlane-height": "Подеси висину стазе", + "set-swimlane-height-value": "Висина стазе (у пикселима)", + "swimlane-height-error-message": "Висина стазе мора бити позитиван број", + "add-swimlane": "Додај стазу", + "add-subtask": "Додај подзадатак", + "add-checklist": "Додај списак за обавити", + "add-checklist-item": "Додај нову ставку на списак", + "close-add-checklist-item": "Затвори образац за додавање ставке на списак за обавити", + "close-edit-checklist-item": "Затвори образац за уређивање ставке списка за обавити", + "convertChecklistItemToCardPopup-title": "Претвори ставку за обавити у облик картице са задатком", + "add-cover": "Додајте насловну слику на мини картицу", "add-label": "Додај налепницу", "add-list": "Додај деоницу", "add-after-list": "Додај након листе", "add-members": "Прими сараднике", - "added": "додао", - "addMemberPopup-title": "Прими сараднике", + "added": "Додао", + "addMemberPopup-title": "Сарадници", "memberPopup-title": "Избор сарадника", "admin": "Управник", - "admin-desc": "Има увид и пун приступ у све предмете из ових списа. У овим списима може да бира сараднике, поставља правила рада и чита записник.", - "admin-announcement": "Јавни разглас", - "admin-announcement-active": "Пусти на јавни разглас", - "admin-announcement-title": "Јавно саопштење управника:", - "all-boards": "Полица са списима", - "and-n-other-card": "И __count__ други предмет", - "and-n-other-card_plural": "И __count__ других предмета", + "admin-desc": "Може да гледа и мења картице, удаљи сараднике и мења правила књиге пословања", + "admin-announcement": "Најава", + "admin-announcement-active": "Дозволи системска обавештења", + "admin-announcement-title": "Обавештење од управника", + "all-boards": "Све пословне књиге", + "and-n-other-card": "И __count__ осталих картица", + "and-n-other-card_plural": "И __count__ осталих картица", "apply": "Примени", "app-is-offline": "Исчитавам, молим да сачекате. Освежавање стране ће изазвати губитак података. Ако повлачење података не ради, молим да проверите да ли је сервер заустављен.", "app-try-reconnect": "Покушавам да се поново повежем.", - "archive": "Спакуј у архиву", - "archive-all": "Спакуј све у архиву", - "archive-board": "Спакуј ове списе у архиву", - "archive-board-confirm": "Да ли сте сигурни да желите да спакујете ове списе у архив?", - "archive-card": "Спакуј предмет у архиву", - "archive-list": "Спакуј овај део у архиву", - "archive-swimlane": "Спакуј цео поступак у архиву", - "archive-selection": "Спакуј изабрано у архиву", - "archiveBoardPopup-title": "Архивираћете ове списе?", - "archived-items": "Архива", - "archived-boards": "Архивирани списи", - "restore-board": "Извуци списе из архиве", - "no-archived-boards": "Архива је празна!", + "archive": "Премести у архиву", + "archive-all": "Премести све у архиву", + "archive-board": "Премести пословну књигу у архиву", + "archive-board-confirm": "Are you sure you want to archive this board?", + "archive-card": "Премести картицу са задатком у архиву", + "archive-list": "Премести деоницу у архиву", + "archive-swimlane": "Премести стазу у архиву", + "archive-selection": "Премести изабрано у архиву", + "archiveBoardPopup-title": "Преместићете ову пословну књигу у архиву?", + "archived-items": "Архивирај", + "archived-boards": "Пословне књиге из архиве", + "restore-board": "Извуци пословну књигу из архиве", + "no-archived-boards": "Нема књига пословања у архиви.", "archives": "Архива", - "template": "Образац", - "templates": "Обрасци", - "template-container": "Сандук са обрасцима", - "add-template-container": "Додај сандук са обрасцима", + "template": "Предложак", + "templates": "Предлошци", + "template-container": "Сандук са предлошцима", + "add-template-container": "Додај сандук са предлошцима", "assign-member": "Додели члана", "attached": "Приложено", "attachment": "Приложени документ", "attachment-delete-pop": "Брисање приложеног документа је трајно. Опозив ове радње неће бити могућ.", "attachmentDeletePopup-title": "Обрисаћете приложени документ?", - "attachments": "Предметна грађа", - "auto-watch": "Чим настану нови списи почни да прикупљаш све промене", - "avatar-too-big": "Слика је превелика (__size__ max)", + "attachments": "Приложени документи", + "auto-watch": "Чим се књига пословања отвори почни да је пратиш", + "avatar-too-big": "Ова сличица је превелика (__size__ max)", "back": "Назад", - "board-change-color": "Боја омота списа", - "board-change-background-image": "Подлога списа", - "board-background-image-url": "Веза до слике", - "add-background-image": "Искористи слику као мотив за подлогу", - "remove-background-image": "Уклони подлогу", - "show-at-all-boards-page": "Смести на полицу са списима", - "board-info-on-my-boards": "Списи у мојој надлежности", - "boardInfoOnMyBoardsPopup-title": "Списи у мојој надлежности", - "boardInfoOnMyBoards-title": "Списи у мојој надлежности", - "show-card-counter-per-list": "Прикажи бројач предмета на сваком делу тока поступка", - "show-board_members-avatar": "Прикажи слике сарадника на омоту списа", - "board_members": "Читава сарадничка мрежа", - "card_members": "Мрежа сарадника на овом предмету у овим списима", - "board_assignees": "Сви пуномоћници из свих предмета у овим списима", - "card_assignees": "Сви пуномоћници текућег предмета у овим списима", + "board-change-color": "Обоји корице књиге пословања", + "board-change-background-image": "Насловница пословне књиге", + "board-background-image-url": "Веза до слике за насловницу пословне књиге", + "add-background-image": "Додај позадинску слику", + "remove-background-image": "Уклони позадинску слику", + "show-at-all-boards-page" : "Прикажи на полазној страни где су све пословне књиге", + "board-info-on-my-boards" : "Правила за све књиге пословања", + "boardInfoOnMyBoardsPopup-title" : "Правила за све књиге пословања", + "boardInfoOnMyBoards-title": "Правила за све пословне књиге", + "show-card-counter-per-list": "Прикажи број картица на деоници", + "show-board_members-avatar": "Прикажи сличице сарадника/корисника пословне књиге", "board-nb-stars": "%s звездица", - "board-not-found": "Спис није пронађен", - "board-private-info": "Ови списи су <strong>видљиви само сарадницима<strong>.", - "board-public-info": "Ови списи су <strong>видљиви свима<strong>.", - "board-drag-drop-reorder-or-click-open": "Притисните и задржите, превуците па отпустите да би пресложили списе. Притисните на деловодни број да би отворили ове списе.", - "boardChangeColorPopup-title": "Промени боју омота ових списа", - "boardChangeBackgroundImagePopup-title": "Списи на осликаној подлози", - "allBoardsChangeColorPopup-title": "Зид у боји", - "allBoardsChangeBackgroundImagePopup-title": "Осликани зид", - "boardChangeTitlePopup-title": "Деловодни број и опис", - "boardChangeVisibilityPopup-title": "Промена тајности ових списа", - "boardChangeWatchPopup-title": "Пријем обавештења", - "boardMenuPopup-title": "Рад са списима", + "board-not-found": "Књига пословања није пронађена", + "board-private-info": "Ова пословна књига ће бити <strong>приватна<strong>.", + "board-public-info": "Ова пословна књига ће бити <strong>јавна<strong>.", + "board-drag-drop-reorder-or-click-open": "Држите и пренесите да би пресложили иконице пословне књиге. Притисните на иконицу да отворите књигу пословања.", + "boardChangeColorPopup-title": "Промени позадину насловне стране књиге пословања", + "boardChangeBackgroundImagePopup-title": "Промена насловнице пословне књиге", + "allBoardsChangeColorPopup-title": "Обоји корице књиге пословања", + "allBoardsChangeBackgroundImagePopup-title": "Насловница пословне књиге", + "boardChangeTitlePopup-title": "Преименуј наслов на пословној књизи", + "boardChangeVisibilityPopup-title": "Промени видљивост", + "boardChangeWatchPopup-title": "Промени праћење", + "boardMenuPopup-title": "Правила коришћења пословне књиге", "allBoardsMenuPopup-title": "Подешавања", - "boardChangeViewPopup-title": "Поглед на списе", - "boards": "Списи", - "board-view": "Поглед на списе", - "desktop-mode": "Приказ прилагођен за екран рачунара", - "mobile-mode": "Приказ прилагођен за екран мобилног уређаја", - "mobile-desktop-toggle": "Замени приказ прилагођен за рачунар/мобилни", - "zoom-in": "Приближи", - "zoom-out": "Одаљи", - "click-to-change-zoom": "Лупа", - "zoom-level": "Ниво", - "enter-zoom-level": "Задајте ниво (50-300%):", + "boardChangeViewPopup-title": "Распоред у књизи пословања", + "boards": "Књиге пословања", + "board-view": "Прегледност пословне књиге", + "desktop-mode": "Desktop Mode", + "mobile-mode": "Mobile Mode", + "mobile-desktop-toggle": "Toggle between Mobile and Desktop Mode", + "zoom-in": "Zoom In", + "zoom-out": "Zoom Out", + "click-to-change-zoom": "Click to change zoom level", + "zoom-level": "Zoom Level", + "enter-zoom-level": "Enter zoom level (50-300%):", "board-view-cal": "Календар", - "board-view-swimlanes": "Врсте поступака", - "board-view-collapse": "Скупи", + "board-view-swimlanes": "Стазе", + "board-view-collapse": "Сажми", "board-view-gantt": "Гант", "board-view-lists": "Деонице", - "bucket-example": "Деловодни број (на пример 4/2025)", - "calendar-previous-month-label": "Претходни месец", - "calendar-next-month-label": "Наредни месец", + "bucket-example": "Like \"Bucket List\" for example", + "calendar-previous-month-label": "Previous Month", + "calendar-next-month-label": "Next Month", "cancel": "Откажи", - "card-archived": "Предмет је архивиран.", - "board-archived": "Ови списи су премештени у архиву.", - "card-comments-title": "У овом предмету у расправи је исказано %s мишљења.", - "card-delete-notice": "Брисање је трајно. Изгубићете све у вези са овим предметом.", - "card-delete-pop": "Читав записник ће бити уклоњен и предмет неће моћи бити опорављен. Опозив ове радње неће бити могућ.", - "card-delete-suggest-archive": "Оно што можете је да предмете исчупате из списа архивирањем (тада се записник чува).", - "card-archive-pop": "Након архивирања, предмет више неће бити видљив у овом делу тока поступка.", - "card-archive-suggest-cancel": "Касније ипак можете извући и вратити предмет из архиве.", - "card-due": "Орочен", - "card-due-on": "Предмету истиче рок дана", + "card-archived": "Ова картица је архивирана.", + "board-archived": "Ова књига пословања је премештена у архиву.", + "card-comments-title": "На овој картици је исказано %s мишљења.", + "card-delete-notice": "Брисање је трајно. Изгубићете све у вези са овом картицом са задацима.", + "card-delete-pop": "Све радње ће бити уклоњене са списка промена и картица са задатком неће моћи бити опорављена. Опозив ове радње неће бити могућ.", + "card-delete-suggest-archive": "Оно што можете је да картицу са задатком архивирате (чиме се све у вези ње очувава) а где је ипак избацујете из књиге пословања.", + "card-archive-pop": "Након њеног архивирања, картица више неће бити видљива на овој деоници.", + "card-archive-suggest-cancel": "Касније,ипак, можете извући и вратити картицу из архиве.", + "card-due": "Рок", + "card-due-on": "Рок истиче", "card-spent": "Утрошено време", - "card-edit-attachments": "Рад са предметном грађом", - "card-edit-custom-fields": "Придружене рубрике", + "card-edit-attachments": "Уреди прилоге", + "card-edit-custom-fields": "Уреди сасвим нова поља", "card-edit-labels": "Уреди налепнице", "card-edit-members": "Осмисли сарадничку мрежу", - "card-labels-title": "Залепи налепницу на омот предмета.", - "card-members-title": "Одабир сарадника на списима за овај предмет.", - "card-start": "Започет", - "card-start-on": "Предмет започет дана", - "cardAttachmentsPopup-title": "Извор предметне грађе", - "cardCustomField-datePopup-title": "Промени датум у рубрици", - "cardCustomFieldsPopup-title": "Придружене рубрике", - "cardStartVotingPopup-title": "Саветодавни референдум", - "positiveVoteMembersPopup-title": "Гласали „За“", - "negativeVoteMembersPopup-title": "Гласали „Против“", - "card-edit-voting": "Јавно гласање", - "editVoteEndDatePopup-title": "Када се гласачко место затвара", - "allowNonBoardMembers": "Дај право гласа и онима који немају увид у ове списе", - "vote-question": "Питање на јавном гласању", - "vote-public": "Прикажи резултате јавног гласања", + "card-labels-title": "Залепи налепницу на картицу.", + "card-members-title": "Додај или уклони сараднике из књиге пословања са картице.", + "card-start": "Почетак", + "card-start-on": "Почиње", + "cardAttachmentsPopup-title": "Прилог долази са", + "cardCustomField-datePopup-title": "Промени датум", + "cardCustomFieldsPopup-title": "Уреди сасвим нова поља", + "cardStartVotingPopup-title": "Ново гласање", + "positiveVoteMembersPopup-title": "Гласали За", + "negativeVoteMembersPopup-title": "Гласали Против", + "card-edit-voting": "Постави правила гласања", + "editVoteEndDatePopup-title": "Гласачко место се затвара", + "allowNonBoardMembers": "Дозволи свим корисницима са пријавом", + "vote-question": "Гласачко питање", + "vote-public": "Прикажи резултате гласања", "vote-for-it": "за", "vote-against": "против", "deleteVotePopup-title": "Избрисаћете гласање?", "vote-delete-pop": "Брисање има трајни карактер. Све у вези са овим гласањем ће бити неповратно избрисано.", "cardStartPlanningPokerPopup-title": "Започни договор за партију покера", - "card-edit-planning-poker": "Такмичење", + "card-edit-planning-poker": "Договор за партију покера", "editPokerEndDatePopup-title": "Промени крајњи датум до кад траје гласање за партију покера", - "poker-question": "Играмо карте", + "poker-question": "Договор за партију покера", "poker-one": "1", "poker-two": "2", "poker-three": "3", @@ -266,110 +249,100 @@ "set-estimation": "Постави прогнозу", "deletePokerPopup-title": "Уклони договор за партију покера?", "poker-delete-pop": "Брисање има трајни карактер. Изгубићете све радње у вези овог плана за партију покера.", - "cardDeletePopup-title": "Обрисаћете предмет?", - "cardArchivePopup-title": "Архивираћете предмет?", - "cardDetailsActionsPopup-title": "Шире радње на предмету", + "cardDeletePopup-title": "Обрисаћете картицу?", + "cardArchivePopup-title": "Архивираћете картицу?", + "cardDetailsActionsPopup-title": "Радње на картицама са задацима", "cardLabelsPopup-title": "Налепнице", "cardMembersPopup-title": "Сарадничка мрежа", "cardMorePopup-title": "Више", - "cardTemplatePopup-title": "Направи образац", - "cards": "Предмети", - "cards-count": "Предмета", - "cards-count-one": "Предмет", + "cardTemplatePopup-title": "Направи предложак", + "cards": "Картице", + "cards-count": "Картице", + "cards-count-one": "Картица", "casSignIn": "Пријави се користећи CAS", - "cardType-card": "Предмет", - "cardType-linkedCard": "Везани предмет", - "cardType-linkedBoard": "Везани списи", + "cardType-card": "Картица са задацима", + "cardType-linkedCard": "Повезана картица са задацима", + "cardType-linkedBoard": "Повезана деоница", "change": "Промени", - "change-avatar": "Моја слика", + "change-avatar": "Промени сличицу", "change-password": "Промени лозинку", - "change-permissions": "Промени улогу", - "change-settings": "Поставка предмета", - "changeAvatarPopup-title": "Моја слика", - "delete-avatar-confirm": "Да ли сте сигурни да желите да уклоните ову слику?", - "deleteAvatarPopup-title": "Уклањате слику?", + "change-permissions": "Промени дозволе", + "change-settings": "Неколико важних правила", + "changeAvatarPopup-title": "Промени сличицу", "changeLanguagePopup-title": "Избор језика", "changePasswordPopup-title": "Промена лозинке", - "changePermissionsPopup-title": "Избор улоге", - "changeSettingsPopup-title": "Општа поставка предмета", - "subtasks": "Издвојени послови", - "checklists": "Предметне радње", - "click-to-star": "Означи звездицом ове списе.", - "click-to-unstar": "Уклони звездицу са ових списа.", + "changePermissionsPopup-title": "Промени дозволе", + "changeSettingsPopup-title": "Неколико важних правила", + "subtasks": "Подзадаци", + "checklists": "Спискови", + "click-to-star": "Притисни да означиш звездицом ову књигу пословања.", + "click-to-unstar": "Притисни да уклониш звездицу са ове пословне књиге.", "click-to-enable-auto-width": "Auto list width disabled. Click to enable.", "click-to-disable-auto-width": "Auto list width enabled. Click to disable.", "auto-list-width": "Auto list width", - "clipboard": "Привремена меморија", + "clipboard": "Из историје или пренеси и испусти", "close": "Заклопи", - "close-board": "Заклопи списе", - "close-board-pop": "Списе можете да повратите притиском на „Ваше име“ ⇨ „Архива“ у горњем десном углу.", - "close-card": "Заклопи предмет", + "close-board": "Заклопи књигу пословања", + "close-board-pop": "Моћи ћете да повратите пословну књигу притиском на дугме “Архив” са почетног заглавља.", + "close-card": "Затвори картицу са задатком", "color-black": "црна", "color-blue": "плава", - "color-crimson": "тамноцрвена", - "color-darkgreen": "тамнозелена", + "color-crimson": "тамно-црвена", + "color-darkgreen": "тамно-зелена", "color-gold": "златна", "color-gray": "сива", "color-green": "зелена", - "color-indigo": "у боји индиго пресликача", - "color-lime": "пастелнозелена", - "color-magenta": "љубичасто-црвена", - "color-mistyrose": "прљаво роза", - "color-navy": "тамноплава", + "color-indigo": "индиго", + "color-lime": "креч", + "color-magenta": "пурпурно-црвена", + "color-mistyrose": "магловито роза", + "color-navy": "морнарско плава", "color-orange": "наранџаста", - "color-paleturquoise": "бледозелена", - "color-peachpuff": "у боји кајсије", + "color-paleturquoise": "бледо-зелена", + "color-peachpuff": "кајсија", "color-pink": "пинк", - "color-plum": "модра", + "color-plum": "шљива", "color-purple": "љубичаста", "color-red": "црвена", - "color-saddlebrown": "у боји кестена", + "color-saddlebrown": "кожно седло", "color-silver": "сребрна", "color-sky": "небеско плава", - "color-slateblue": "загаситоплава", + "color-slateblue": "загасито плава", "color-white": "бела", "color-yellow": "жута", - "unset-color": "Безбојно", - "comments": "Расправа", + "unset-color": "Обриши подешавање", + "comments": "Comments", "comment": "Изнеси мишљење", - "comment-placeholder": "Место за расправу", - "comment-only": "Стручни саветник", - "comment-only-desc": "Има право увида у све предмете и може да учествује у свим расправама.", - "comment-assigned-only": "Спољни стручни саветник", - "comment-assigned-only-desc": "Једино може да има право увида и узме учешће у расправама у додељеним му предметима.", - "comment-delete": "Да ли сте сигурни да желите да повучете изнешено мишљење?", + "comment-placeholder": "Простор за изношење мишљења", + "comment-only": "Износи мишљење", + "comment-only-desc": "Може да изнесе мишљење само на картицама са задацима.", + "comment-delete": "Да ли сте сигурни да желите да уклоните изнешено мишљење?", "deleteCommentPopup-title": "Повлачите мишљење?", - "no-comments": "Посматрач", - "no-comments-desc": "Без права на расправу и права на увид у записник", - "read-only": "Читалац", - "read-only-desc": "Има право увида у све предмете.", - "read-assigned-only": "Спољни читалац", - "read-assigned-only-desc": "Има право увида само у додељене предмете.", - "worker": "Приправник", - "worker-desc": "Може да ради помоћне послове - да премешта предмете, бирa оне које ће пратити и да учествује у расправи. ", + "no-comments": "Нико није дао мишљење", + "no-comments-desc": "Не може да види изнешена мишљења и догађаје.", + "worker": "Радник", + "worker-desc": "Може само да помера картице са задацима, додељује их себи и да даје мишљење. ", "computer": "Рачунар", - "confirm-subtask-delete-popup": "Да ли сте сигурни да желите да избришете овај издвојени посао?", - "confirm-checklist-delete-popup": "Да ли сте сигурни да желите да избришете ову предметну радњу?", - "subtaskDeletePopup-title": "Избрисаћете издвојени посао?", - "checklistDeletePopup-title": "Избрисаћете предметну радњу?", - "checklistItemDeletePopup-title": "Избрисаћете ставку из предметне радње?", - "copy-card-link-to-clipboard": "Причувај везу на кратко", - "copy-text-to-clipboard": "Причувај текст на кратко", - "linkCardPopup-title": "Повежи предмет", + "confirm-subtask-delete-popup": "Да ли сте сигурни да желите да избришете подзадатак?", + "confirm-checklist-delete-popup": "Да ли сте сигурни да желите да избришете овај списак за обавити?", + "subtaskDeletePopup-title": "Избрисаћете подзадатак?", + "checklistDeletePopup-title": "Избрисаћете списак за обавити?", + "copy-card-link-to-clipboard": "Причувај везу до картице у привременој меморији", + "copy-text-to-clipboard": "Причувај текст у привременој меморији", + "linkCardPopup-title": "Повежи картицу", "searchElementPopup-title": "Претрага", - "copyCardPopup-title": "Умножи предмет", - "copyManyCardsPopup-title": "Расади попуњен образац", - "copyManyCardsPopup-instructions": "Наслови и описи одредишних предмета треба да имају следећи JSON облик:", - "copyManyCardsPopup-format": "[ {\"наслов\": \"Наслов првог предмета\", \"опис\":\"Опис првог предмета\"}, {\"наслов\":\"Наслов другог предмета\",\"опис\":\"Опис другог предмета\"},{\"наслов\":\"Наслов последњег предмете\",\"опис\":\"Опис последњег предмета\"} ]", + "copyCardPopup-title": "Умножи картицу", + "copyManyCardsPopup-title": "Умножи предложак на више картица", + "copyManyCardsPopup-instructions": "Наслови и описи одредишних картица у следећем JSON облику", + "copyManyCardsPopup-format": "[ {\"наслов\": \"Наслов прве картице\", \"опис\":\"Опис прве картице\"}, {\"наслов\":\"Наслов друге картице\",\"опис\":\"Опис друге картице\"},{\"наслов\":\"Наслов последње картице\",\"опис\":\"Опис последње картице\"} ]", "create": "Уведи", - "createBoardPopup-title": "Увод нових списа", - "createTemplateContainerPopup-title": "Додај сандук са предлошцима", - "chooseBoardSourcePopup-title": "Унеси спољне списе", + "createBoardPopup-title": "Уведи нову пословну књигу", + "chooseBoardSourcePopup-title": "Уведи пословну књигу", "createLabelPopup-title": "Нова налепница", - "createCustomField": "Нова рубрика", - "createCustomFieldPopup-title": "Нова рубрика", + "createCustomField": "Направи сасвим ново поље", + "createCustomFieldPopup-title": "Направи сасвим ново поље", "current": "текуће", - "custom-field-delete-pop": "Опозив ове радње неће бити могућ. Радњом се уклања ова придружена рубрика из свих предмета и уништива њена претходна историја употребе.", + "custom-field-delete-pop": "Опозив ове радње неће бити могућ. Овим се уклања ово сасвим ново поље из свих картица и уништива његова историја.", "custom-field-checkbox": "Поље за штиклирање", "custom-field-currency": "Валута", "custom-field-currency-option": "Код валуте", @@ -381,16 +354,16 @@ "custom-field-dropdown-unknown": "(непознато)", "custom-field-number": "Број", "custom-field-text": "Текст", - "custom-fields": "Придружене рубрике", + "custom-fields": "Сасвим нова поља", "date": "Датум", - "date-format": "Запис", - "date-format-yyyy-mm-dd": "година-месец-дан", - "date-format-dd-mm-yyyy": "дан-месец-година", - "date-format-mm-dd-yyyy": "месец-дан-година", - "decline": "Одбијам", - "default-avatar": "иницијали уместо слике", + "date-format": "Date Format", + "date-format-yyyy-mm-dd": "YYYY-MM-DD", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-mm-dd-yyyy": "MM-DD-YYYY", + "decline": "Одбиј", + "default-avatar": "Унапред изабрана сличица", "delete": "Уклони", - "deleteCustomFieldPopup-title": "Обрисаћете ову придружену рубрику?", + "deleteCustomFieldPopup-title": "Обрисаћете сасвим ново поље?", "deleteLabelPopup-title": "Одлепићете све овакве налепнице?", "description": "Опис", "disambiguateMultiLabelPopup-title": "Недвосмислене радње на налепницама", @@ -400,122 +373,122 @@ "download": "Преузми", "edit": "Уреди", "edit-avatar": "Моја слика", - "edit-profile": "Лични подаци", - "edit-wip-limit": "Укини границу", - "soft-wip-limit": "Мека граница броја предмета", - "editCardStartDatePopup-title": "Кад сте започели рад на предмету", - "editCardDueDatePopup-title": "Крајњи рок за предмет", - "editCustomFieldPopup-title": "Измени наслов рубрике", + "edit-profile": "Моји лични подаци", + "edit-wip-limit": "Уреди ограничење броја послова", + "soft-wip-limit": "Мека граница броја послова", + "editCardStartDatePopup-title": "Кад је задатак започет", + "editCardDueDatePopup-title": "Крајњи рок за испуњење задатка", + "editCustomFieldPopup-title": "Измени поље", "addReactionPopup-title": "Реакција на објаву", - "editCardSpentTimePopup-title": "Утрошак времена", + "editCardSpentTimePopup-title": "Промени утрошено време", "editLabelPopup-title": "Постојећа налепница", "editNotificationPopup-title": "Измени обавештење", "editProfilePopup-title": "Лични подаци", "email": "Е-пошта", - "email-address": "Адреса електронске поште", + "email-address": "Email Address", "email-enrollAccount-subject": "За Вас је направљен један налог на __siteName__", "email-enrollAccount-text": "Здраво __user__,\n\nДа би почели да користите услугу, једноставно притисните на везу која је испод.\n\n__url__\n\nХвала.", - "email-fail": "Неуспело слање е-поште!", + "email-fail": "Неуспело слање е-поште", "email-fail-text": "Грешка при покушају слања е-поште", "email-invalid": "Неисправна адреса е-поште", "email-invite": "Позив преко е-поште", "email-invite-subject": "__inviter__ Вам је послао позивницу", - "email-invite-text": "Поштовани __user__,\n\n__inviter__ Вам је омогућио пун увид у \"__board__\" списе и нада се заједничкој сарадњи.\n\nМолим да испратите везу испод:\n\n__url__\n\nХвала.", + "email-invite-text": "Драги __user__,\n\n__inviter__ Вас позива на заједничку сарадњу кроз \"__board__\" пословну књигу.\n\nМолим да испратите везу испод:\n\n__url__\n\nХвала.", "email-resetPassword-subject": "Задајте поново вашу лозинку на страници __siteName__", "email-resetPassword-text": "Добар дан __user__,\n\nДа би сте поново задали вашу лозинку, једноставно притисните на везу испод.\n\n__url__\n\nХвала.", "email-sent": "Е-пошта је послана", "email-verifyEmail-subject": "Потврдите Вашу адресу е-поште на страници __siteName__", "email-verifyEmail-text": "Добар дан __user__,\n\nДа би сте потврдили ваш налог за е-пошту, једноставно притисните на везу испод.\n\n__url__\n\nХвала.", "enable-vertical-scrollbars": "Enable vertical scrollbars", - "enable-wip-limit": "Уведи ограничење", - "error-board-doesNotExist": "Овакви списи не постоје", - "error-board-notAdmin": "Да би то урадили, треба да будете управник ових списа", - "error-board-notAMember": "Да би то урадили треба да будете сарадник на овим списима", + "enable-wip-limit": "Ограничи број послова", + "error-board-doesNotExist": "Ова пословна књига не постоји", + "error-board-notAdmin": "Да би то урадили, треба да будете администратор/управник ове књиге пословања", + "error-board-notAMember": "Да би то урадили треба да будете сарадник у овој пословној књизи", "error-json-malformed": "То што сте укуцали нема исправан JSON облик", "error-json-schema": "Ваши подаци у JSON облику не укључују исправне информације у предвиђеном облику", "error-csv-schema": "Ваш CSV(Вредности раздвојене зарезом)/TSV (Вредности раздвојене табулатором) не укључује исправну информацију у предвиђеном облику", "error-list-doesNotExist": "Ова деоница не постоји", "error-user-doesNotExist": "Сарадник не постоји", - "error-user-notAllowSelf": "Не можете сами себе да позовете", - "error-user-notCreated": "Сарадник није уписан", - "error-username-taken": "Кориснички налог је већ у употреби", - "error-orgname-taken": "Ово (пословно) име странке је већ у употреби", - "error-teamname-taken": "Ово име правног тима је већ у употреби", + "error-user-notAllowSelf": "Не можеш да позовеш сам себе", + "error-user-notCreated": "Сарадник не постоји", + "error-username-taken": "Корисничко име је већ у употреби", + "error-orgname-taken": "Ово име предузећа је већ у употреби", + "error-teamname-taken": "Ово име тима је већ у употреби", "error-email-taken": "Ова адреса е-поште је већ у употреби", - "export-board": "Претвори списе", - "export-board-json": "Претварање списа у JSON", - "export-board-csv": "Претварање списа у CSV", - "export-board-tsv": "Претварање списа у TSV", - "export-board-excel": "Претварање списа у Excel", - "user-can-not-export-excel": "Корисник не може да претвори списе у Excel облик", - "export-board-html": "Претварање списа у HTML", - "export-card": "Претвори предмет", - "export-card-pdf": "Претвори предмет у PDF облик", - "user-can-not-export-card-to-pdf": "Корисник не може да претвори садржај предмета у PDF облик", - "exportBoardPopup-title": "Претварање списа", - "exportCardPopup-title": "Претвори предмет", + "export-board": "Преведи пословну књигу", + "export-board-json": "Преведи пословну књигу у JSON", + "export-board-csv": "Преведи пословну књигу у CSV", + "export-board-tsv": "Преведи пословну књигу у TSV", + "export-board-excel": "Преведи пословну књигу у Excel", + "user-can-not-export-excel": "Корисник не може да преводи у Excel", + "export-board-html": "Преведи пословну књигу у HTML", + "export-card": "Преведи картицу са задатком", + "export-card-pdf": "Преведи картицу са задатком у PDF", + "user-can-not-export-card-to-pdf": "Корисник не може да преведе садржај картице са задатком у PDF", + "exportBoardPopup-title": "Превођење пословне књиге", + "exportCardPopup-title": "Превођење картице са задатком", "sort": "Редослед", "sorted": "Сложено", - "remove-sort": "Измешај предмете", - "sort-desc": "Притисните да би сложили делове поступка", - "list-sort-by": "Поређај делове поступка по:", - "list-label-modifiedAt": "Времену последње измене", - "list-label-title": "Називу дела поступка", - "list-label-sort": "Вашем личном нахођењу", - "list-label-short-modifiedAt": "(П)", - "list-label-short-title": "(Н)", - "list-label-short-sort": "(Л)", + "remove-sort": "Разбацај", + "sort-desc": "Притисните да би сложили деонице", + "list-sort-by": "Поређај деоницу по:", + "list-label-modifiedAt": "Време последње измене", + "list-label-title": "Назив деонице", + "list-label-sort": "Vaš ručni nalog", + "list-label-short-modifiedAt": "(P)", + "list-label-short-title": "(N)", + "list-label-short-sort": "(R)", "filter": "Издвајање", - "filter-cards": "Издвој предмете или делове поступка", + "filter-cards": "Издвој картице или деонице", "filter-dates-label": "Издвој по датуму", - "filter-no-due-date": "Где није постављен рок", - "filter-overdue": "Где је пробијен рок", - "filter-due-today": "Где рок истиче данас", - "filter-due-this-week": "Где рок истиче ове недеље", - "filter-due-next-week": "Где рок истиче следеће недеље", - "filter-due-tomorrow": "Где рок истиче сутрадан", - "list-filter-label": "Издвој део поступка по наслову", + "filter-no-due-date": "Нема датума истека", + "filter-overdue": "Прекорачено време", + "filter-due-today": "Рок истиче данас", + "filter-due-this-week": "Рок истиче ове недеље", + "filter-due-next-week": "Рок истиче следеће недеље", + "filter-due-tomorrow": "Рок истиче сутрадан", + "list-filter-label": "Издвој деонице по наслову", "filter-clear": "Прекини са издвајањем", "filter-labels-label": "Издвој по налепници", "filter-no-label": "Нема налепницу", - "filter-member-label": "Издвој по надлежном сараднику", - "filter-no-member": "Нико није надлежан", - "filter-assignee-label": "Издвој по пуномоћнику", - "filter-no-assignee": "Где никоме није дата пуномоћ", - "filter-custom-fields-label": "Издвој по рубрикама", - "filter-no-custom-fields": "Где нема рубрика", - "filter-show-archive": "Прикажи архивиране делове поступка", - "filter-hide-empty": "Сакриј делове поступка без предмета", + "filter-member-label": "Издвој по сараднику", + "filter-no-member": "Нема сарадника", + "filter-assignee-label": "Издвој по задужењу", + "filter-no-assignee": "Нико није задужен", + "filter-custom-fields-label": "Издвој по сасвим новим пољима", + "filter-no-custom-fields": "Нема сасвим нових поља", + "filter-show-archive": "Прикажи архивиране деонице", + "filter-hide-empty": "Сакриј празне деонице", "filter-on": "Издваја се", - "filter-on-desc": "Сад издвајате предмете из ових списа. Притисните овде да одредите како се издвајање врши.", + "filter-on-desc": "Издвајате картице из ове пословне књиге. Притисните овде да одредите како се издвајање врши.", "filter-to-selection": "Издвајање по изабраном", - "other-filters-label": "Преостала издвајања", + "other-filters-label": "Другачије издвајање", "advanced-filter-label": "Напредно издвајање", - "advanced-filter-description": "Напредно издвајање Вам омогућава да напишете ниску која садржи следеће операторе: == != <= >= && || ( ) Размак се користи да раздвоји операторе. Можете да издвајате поља из придружених рубрика позивајући се на њихова имена и вредности. На пример: ИмеПридруженогПоља == Вредност. Напомена: Уколико предметна поља или њихове вредности садрже размак треба да га обухватите у једноструке наводнике. На пример: 'Поље 1' == 'Вредност 1'. Да би избегли контролне знаке (' \\\\/) , можете да користите \\\\. Например: Поље1 == I\\\\'m . Такође можете измешати више услова. На пример: Поље1 == Вредност1 || Поље1 == Вредност2. Провера услова се обавља са лева на десно. Додавањем заграда ћете утицати на проверу. На пример: Поље1 == Вредност1 && ( Поље2 == Вредност2 || Поље2 == Вредност3 ). Такође можете претраживати поља програмерским изразима: Поље1 == /Tes.*/i", + "advanced-filter-description": "Напредно издвајање Вам омогућава да напишете ниску која садржи следеће оператореAdvanced: == != <= >= && || ( ) Празно место се користи да раздвоји операторе. Можете да издвајате сва поља која сте измислили пишући њихова имена и вредности. На пример: ИмеИзмишљеногПоља == Вредност. Напомена: Уколико предметна поља или њихове вредности садрже празна места треба да их обухватите у једноструке наводнике. На пример: 'Поље 1' == 'Вредност 1'. Да би избегли контролне знаке (' \\\\/) , можете да користите \\\\. Например: Поље1 == Ал\\\\' је дугачко поље. Такође можете измешати више услова. На пример: Поље1 == Вредност1 || Поље1 == Вредност2. Провера услова се обавља са лева на десно. Додавањем заграда ћете утицати на проверу. На пример: Поље1 == Вредност1 && ( Поље2 == Вредност2 || Поље2 == Вредност3 ). Такође можете претраживати поља програмерским изразима: Поље1 == /Tes.*/i", "fullname": "Име и презиме", - "header-logo-title": "Вратите се на полицу са Вашим списима.", - "show-activities": "Писани записник", - "headerBarCreateBoardPopup-title": "Увод нових списа", + "header-logo-title": "Вратите се на полицу са Вашим пословним књигама.", + "show-activities": "Show Activities", + "headerBarCreateBoardPopup-title": "Отвори нову књигу пословања", "home": "Почетна", "import": "Унеси", - "impersonate-user": "Увуци се у налог овог корисника", + "impersonate-user": "Лажно представљен корисник", "link": "Веза", - "import-board": "унеси спољне списе", - "import-board-c": "Извор спољних списа", - "import-board-title-trello": "Trello", - "import-board-title-wekan": "Wekan", - "import-board-title-csv": "CSV/TSV", - "from-trello": "Извор Trello", - "from-wekan": "Извор претходни Wekan подаци", - "from-csv": "Извор CSV/TSV датотека", - "import-board-instruction-trello": "У Вашоим Trello списима, идите у 'Menu', затим 'More', 'Print and Export', 'Export JSON', и умножите представљени текст.", + "import-board": "уведи пословну књигу", + "import-board-c": "Уведи пословну књигу", + "import-board-title-trello": "Уведи књигу пословања из Trella", + "import-board-title-wekan": "Уведи пословну књигу из претходног преноса", + "import-board-title-csv": "Уведи једну CSV/TSV пословну књигу", + "from-trello": "Из Trello", + "from-wekan": "Из претходног превода", + "from-csv": "Из CSV/TSV", + "import-board-instruction-trello": "У Вашој Trello пословној књизи, идите у 'Menu', затим 'More', 'Print and Export', 'Export JSON', и умножите представљени текст.", "import-board-instruction-csv": "Овде залепите Ваше CSV податке (вредности раздвојене зарезом) односно TSV податке (вредности раздвојене табулатором) .", - "import-board-instruction-wekan": "У Вашим списима, изаберите ставке 'Мени', затим 'Унеси списе' и на крају умножите сав садржај/текст из преузете датотеке.", - "import-board-instruction-about-errors": "Ако се јаве грешке при уношењу списа то не значи да унос није успео!Проверите јел се спис појавио у одељку „Списи у мојој надлежности“.", + "import-board-instruction-wekan": "У Вашој књизи пословања, изаберите ставке 'Мени', затим 'Преведи пословни књигу' и на крају умножите сав садржај/текст из преузете датотеке.", + "import-board-instruction-about-errors": "Ако се јаве грешке при уношење пословне књиге то не значи да унос није успео!Проверите јел се књига појавила у одељку Моје пословне књиге.", "import-json-placeholder": "Овде сместите Ваше исправне JSON податке", "import-csv-placeholder": "Овде сместите Ваше исправне CSV/TSV податке", "import-map-members": "Мапирај сараднике", - "import-members-map": "Списи које сте унели имају постављену мрежу сарадника. Молим да мапирате сараднике из те мреже на Ваше сараднике", + "import-members-map": "Пословна књига коју сте унели има постављену мрежу сарадника. Молим да мапирате сараднике из те мреже на Ваше сараднике", "import-members-map-note": "Белешка: Немапирани сарадници ће бити додељени на тренутног корисника.", "import-show-user-mapping": "Испрегледај како су сарадници измапирани", "import-user-select": "Изаберите Вашег познатог сарадника којег желите да корисите као овог", @@ -526,190 +499,183 @@ "invalid-time": "Нетачно време", "invalid-user": "Непознат корисник", "joined": "придружен", - "just-invited": "Управо сте позвани да узмете учешће у раду на овим списима", + "just-invited": "Управо сте позвани у ову књигу пословања", "keyboard-shortcuts": "Пречице на тастатури", "label-create": "Уведи нову налепницу", "label-default": "%s налепница (подразумевано)", - "label-delete-pop": "Пажња. Опозив ове радње неће бити могућ! Изабрана налепница ће бити одлепљена/уклоњена из свих предмета и њена историја ће исчезнути.", + "label-delete-pop": "Пажња. Опозив ове радње неће бити могућ! Изабрана налепница ће бити одлепљена/уклоњена са свих картица са задацима и њена историја ће исчезнути.", "labels": "Налепнице", "language": "Језик", "last-admin-desc": "Не можете да мењате задужења јер мора постојати барем један управник/администратор.", - "leave-board": "Раздужи предмете из списа", - "leave-board-pop": "Да ли сте сигурни да желите да раздужите предмете из списа __boardTitle__? Бићете одстрањени са свих тих предмета.", - "leaveBoardPopup-title": "Раздужићете списе ?", - "link-card": "Повежи предмете", - "list-archive-cards": "Архивирај све предмете из овог дела", - "list-archive-cards-pop": "Овим склањате све предмете из овог дела поступка из списа. Да испрегледате тако архивиране предмете и вратите их натраг у ове списе притисните на “Мени” > “Архива”.", - "list-move-cards": "Премести све предмете из овог дела поступка", - "list-select-cards": "Изабери све предмете", - "set-color-list": "Обоји", - "listActionPopup-title": "Радње у овом делу поступка", - "settingsUserPopup-title": "Рад са сарадницима", - "settingsTeamPopup-title": "Рад са правним тимовима", - "settingsOrgPopup-title": "Рад са странкама", - "swimlaneActionPopup-title": "Радње у овој врсти поступка", - "swimlaneAddPopup-title": "Додаје испод врсту поступка", - "listImportCardPopup-title": "Унеси једнан Trello предмет", + "leave-board": "Раздужи књигу пословања", + "leave-board-pop": "Да ли сте сигурни да желите да раздужите пословну књигу __boardTitle__? Бићете одстрањени са свих задатака из ове књиге пословања.", + "leaveBoardPopup-title": "Раздужићете пословну књигу ?", + "link-card": "Веза до ове картице", + "list-archive-cards": "Премести у архив све картице са задацима са ове деонице", + "list-archive-cards-pop": "Овим склањате све задатке на овој деоници из књиге пословања. Да испрегледате архивиране задатке и вратите их натраг у књигу пословања притисните на “Мени” > “Архив”.", + "list-move-cards": "Премести све картице на овој деоници", + "list-select-cards": "Изабери све картице на овој деоници", + "set-color-list": "Обоји деоницу", + "listActionPopup-title": "Радње на деоници", + "settingsUserPopup-title": "Корисничка подешавања", + "settingsTeamPopup-title": "Тимска подешавања", + "settingsOrgPopup-title": "Подешавања предузећа", + "swimlaneActionPopup-title": "Радње на стазама", + "swimlaneAddPopup-title": "Додаје једну стазу испод", + "listImportCardPopup-title": "Унеси једну Trello картицу", "listImportCardsTsvPopup-title": "Унеси Excel CSV/TSV", "listMorePopup-title": "Више", "link-list": "Веза до ове деонице", - "list-delete-pop": "Читав записник са овог дела поступка биће уклоњен и нећете га моћи опоравити. Опозив ове радње неће бити могућ.", - "list-delete-suggest-archive": "Боље је да преместите читав део поступка у Архив чиме чувате записник а део поступка ипак склањате из списа.", - "lists": "Делови поступка", - "swimlanes": "Врсте поступака", - "calendar": "Календар", - "gantt": "Гант", - "log-out": "Одјави се", + "list-delete-pop": "Све досадашње забележене радње на деоници биће уклоњене и нећете моћи опоравити деоницу. Опозив ове радње неће бити могућ.", + "list-delete-suggest-archive": "Можете да преместите целу неку деоницу у Архив чиме чувате историју деонице а ипак је склањате из књиге пословања.", + "lists": "Деонице", + "swimlanes": "Стазе", + "log-out": "Одјава", "log-in": "Пријави се", - "loginPopup-title": "Пријавница", + "loginPopup-title": "Пријава", "memberMenuPopup-title": "Сарадник", - "grey-icons": "Сиви дом", "members": "Сарадници", "menu": "Мени", "move-selection": "Премести изабрано", - "moveCardPopup-title": "Премести предмет", + "moveCardPopup-title": "Премести картицу са задатком", "moveCardToBottom-title": "Премести на дно", "moveCardToTop-title": "Премести на врх", "moveSelectionPopup-title": "Премести изабрано", - "copySelectionPopup-title": "Умножи изабрано", - "selection-color": "Боја за изабрано", "multi-selection": "Вишеструк избор", - "multi-selection-label": "Залепите или одлепите налепнице на одабране предмете", - "multi-selection-member": "Одаберите и сараднике", - "multi-selection-on": "Постоји вишеструк избор", - "muted": "Не примај обавештења", - "muted-info": "Нећете чути обавештења кад наступе било какве промене у овим списима", - "my-boards": "Списи у мојој надлежности", + "multi-selection-label": "Изаберите налепницу", + "multi-selection-member": "Изаберите и сарадника", + "multi-selection-on": "Вишеструк избор је омогућен", + "muted": "Утишано", + "muted-info": "Нећете чути обавештење о променама у овој пословној књизи", + "my-boards": "Моје књиге пословања", "name": "Задајте (нови) натпис", - "no-archived-cards": "Нема архивираних предмета.", - "no-archived-lists": "Нема архивираних делова поступака.", - "no-archived-swimlanes": "Нема архивираних врсти поступака.", + "no-archived-cards": "Нама архивираних картица са задацима.", + "no-archived-lists": "Нема архивираних деоница.", + "no-archived-swimlanes": "Нема архивираних стаза.", "no-results": "Нема резултата", - "normal": "Виши сарадник", - "normal-desc": "Може да има и увид и пун приступ у све предмете из ових списа. Не може да поставља правила рада у овим списима.", - "normal-assigned-only": "Спољни виши сарадник", - "normal-assigned-only-desc": "Има увид и пуна права само у оним предметима где има надлежност или је опуномоћен.", - "not-accepted-yet": "Позив још увек није прихваћен", - "notify-participate": "Примајте допунске извештаје при било којој измени предмета које сте сами завели или где сте сарадник", - "notify-watch": "Примајте допунске извештаје при било којој измени списа, делова поступака или предмета које пратите", + "normal": "Нормално", + "normal-desc": "Може да гледа и уређује картице. Не може да мења подешавања.", + "not-accepted-yet": "Позив још није прихваћен", + "notify-participate": "Примајте допунске извештаје за било које картице са задацима у којима учествујете као налогодавац или сарадник", + "notify-watch": "Примајте допунске извештаје за било које пословне књиге, деонице, или картице са задацима које пратите", "optional": "по избору", "or": "или", - "page-maybe-private": "Ови списи су могуће под велом тајности. Можда ћете ипак моћи да их видите кад се <a href='%s'>пријавите овде</a>.", + "page-maybe-private": "Ова страницаје је, могуће, приватна. Можда ћете моћи да је видите кад се <a href='%s'>пријавите</a>.", "page-not-found": "Страница није пронађена.", "password": "Лозинка", - "paste-or-dragdrop": " налепите/пренесите/испустите овде слику (важи само за слике)", + "paste-or-dragdrop": "налепи, или држи и испусти слику на то (важи само за слике)", "participating": "Учествујем", "preview": "Приказ", - "previewAttachedImagePopup-title": "Приказ пре уноса", - "previewClipboardImagePopup-title": "Приказ пре уноса", - "private": "Видљиво сарадницима", - "private-desc": "Ови списи су тајни. Само одобрени сарадници могу да их читају и уређују.", + "previewAttachedImagePopup-title": "Приказ", + "previewClipboardImagePopup-title": "Приказ", + "private": "Приватно", + "private-desc": "Ова пословна књига је приватна. Само одобрени сарадници могу да је читају и уређују.", "profile": "Особина", - "public": "Списи видљиви свима", - "public-desc": "Ови списи су потпуно јавни. Видљиви су сваком ко има везу до њих и појавиће се и у google претрагама. Једино одобрени сарадници могу бити уредници.", - "quick-access-description": "Означите списе звездицом да би овде стајала пречица.", - "remove-cover": "Уклони слику са омота", - "remove-from-board": "Избаци из списа", + "public": "Јавно", + "public-desc": "Ова пословна књига је јавна. Видљива је сваком ко има везу и појавиће се у google претрагама. Једино одобрени сарадници могу бити уредници.", + "quick-access-description": "Означи књигу пословања звездицом да додаш пречицу на ову траку.", + "remove-cover": "Уклоните насловну слику са мини картице", + "remove-from-board": "Избаци из књиге пословања", "remove-label": "Скини налепницу", - "listDeletePopup-title": "Обрисаћете део поступка?", - "remove-member": "Скини сарадника", - "remove-member-from-card": "Скини га са предмета", - "remove-member-pop": "Изузећете __name__ (__username__) из списа __boardTitle__? Сарадник ће бити изузет са свих предмета у списима. Примиће обавештење о томе.", - "removeMemberPopup-title": "Изузећете сарадника?", + "listDeletePopup-title": "Обрисаћете деоницу?", + "remove-member": "Удаљи сарадника", + "remove-member-from-card": "Удаљи га са задатка", + "remove-member-pop": "Удаљићете __name__ (__username__) из __boardTitle__? Сарадник ће бити удаљен са свих задатака из ове књиге пословања. Примиће обавештење о томе.", + "removeMemberPopup-title": "Удаљићете сарадника?", "rename": "Преименуј", - "rename-board": "Преименуј списе", + "rename-board": "Преименуј књигу пословања", "restore": "Опорави", - "rescue-card-description": "Понуди још једну прилику да се забележи несачувани опис предмета пре затварања", - "rescue-card-description-dialogue": "Преписаћете тренутни опис предмета са допуњеним?", + "rescue-card-description": "Понуди још једну прилику да се забележе несачувани описи задатака пре затварања картице", + "rescue-card-description-dialogue": "Преписаћете тренутни опис задатка са Вашим описом?", "save": "Сачувај", "search": "Претрага", - "rules": "Правилник", - "search-cards": "Тражи у насловима предмета, делова поступака и рубрика у овим списима", - "search-example": "Укуцајте све што тражите и притисните ентер", - "select-color": "Изабери боју за овакав поступак", - "select-board": "Изаберите списе", - "set-wip-limit-value": "Поставите границу дозвољеног броја предмета у овом делу поступка", - "setWipLimitPopup-title": "Граница броја предмета", - "shortcut-add-self": "Доделите надлежност себи", - "shortcut-assign-self": "Поверите пуномоћ себи", + "rules": "Правила", + "search-cards": "Тражи у насловима задатака/деоница, новим пољима у овој пословној књизи", + "search-example": "Укуцајте речи/текст које тражите и притисните ентер", + "select-color": "Изаберите (нову) боју", + "select-board": "Изаберите књигу пословања", + "set-wip-limit-value": "Поставите границу за максимални дозвољени број задатака на овој деоници", + "setWipLimitPopup-title": "Поставите ограничење броја послова", + "shortcut-add-self": "Add yourself to current card", + "shortcut-assign-self": "Придружите себе тренутној картици", "shortcut-autocomplete-emoji": "Сам попуни emoji", "shortcut-autocomplete-members": "Сам попуни сараднике", "shortcut-clear-filters": "Прекини издвајање", "shortcut-close-dialog": "Затвори прозорче", - "shortcut-filter-my-cards": "Издвој предмете где имам увид", - "shortcut-filter-my-assigned-cards": "Издвој предмете где сам пуномоћник", - "shortcut-show-shortcuts": "Прикажи овај подсетник са пречицама", - "shortcut-toggle-filterbar": "Алат за издвајање", - "shortcut-toggle-searchbar": "Алат за претрагу", - "shortcut-toggle-sidebar": "Алат за рад са списима", - "show-cards-minimum-count": "Изброј предмете и прикажи тај број тек ако део поступка садржи више од", - "sidebar-open": "Извади алат", - "sidebar-close": "Спакуј алат", - "signupPopup-title": "Јединствени регистар", - "star-board-title": "Означите спис звездицом. Имаће већу тежину и биће постављен на чело.", - "starred-boards": "Списи са звездицом", - "starred-boards-description": "Списи означени звездицом се појављују испред других.", + "shortcut-filter-my-cards": "Издвој задатке", + "shortcut-filter-my-assigned-cards": "Filter my assigned cards", + "shortcut-show-shortcuts": "Прикажи овај списак пречица", + "shortcut-toggle-filterbar": "Укључи/искључи бочни мени за издвајање", + "shortcut-toggle-searchbar": "Укључи/искључи бочни мени за претрагу", + "shortcut-toggle-sidebar": "Укључи/искључи бочни мени пословне књиге", + "show-cards-minimum-count": "Прикажи број картица са задацима ако деоница садржи више од", + "sidebar-open": "Отвори бочну површину", + "sidebar-close": "Затвори бочну површину", + "signupPopup-title": "Направите налог", + "star-board-title": "Притисни да означиш звездицом баш ову књигу пословања. Имаће предност и бити на челу.", + "starred-boards": "Пословне књиге са звездицом", + "starred-boards-description": "Књиге пословања са звездицом се појављују испред других.", "subscribe": "Претплати се", - "team": "Правни тим", - "this-board": "ове списе", - "this-card": "овај предмет", - "spent-time-hours": "Утрошено време (у сатима)", + "team": "Тим", + "this-board": "ова пословна књига", + "this-card": "ова картица са задатком", + "spent-time-hours": "Потрошено време (у сатима)", "overtime-hours": "Прековремени (сати)", "overtime": "Прековремено", - "has-overtime-cards": "Постоје предмети са прековременим радом", - "has-spenttime-cards": "Постоје предмети где сат одбројава", + "has-overtime-cards": "Има задатке на којима је прековремено радио", + "has-spenttime-cards": "Има задатке са мерењем времена", "time": "Време", "title": "Наслов", - "toggle-assignees": "Одабери пуномоћника на предмету по редоследу додавања у увид у списе.", - "toggle-labels": "Лепите и одлепљујете налепнице по броју. Вишеструким избором можете само да лепите", - "remove-labels-multiselect": "Вишеструким избором одлепљујете налепнице", - "tracking": "Прикупљај обавештења из моје надлежности", - "tracking-info": "Бићете обавештени о било каквој промени у оним предметима где сте укључени као сарадник или које сте лично завели.", + "toggle-assignees": "Toggle assignees 1-9 for card (By order of addition to board).", + "toggle-labels": "Укључи/искључи натписе од 1 до 9 за задатак. Вишеструк избор додаје натпис од 1 до 9", + "remove-labels-multiselect": "Вишеструким избором се уклањају натписи од 1 до 9", + "tracking": "Праћење", + "tracking-info": "Бићете обавештени о свим променама на оним картицама са задацима где сте укључени као налогодавац или као сарадник.", "type": "Врста", "unassign-member": "Недодељени сарадник", "unsaved-description": "Нисте сачували опис.", "unwatch": "Скини са мера праћења", "upload": "Подигни", - "upload-avatar": "слика уместо иницијала", - "uploaded-avatar": "Слика је подигнута на сервер", - "uploading-files": "Шаљем датотеке", - "upload-failed": "Слање није успело", - "upload-completed": "Слање је успело", - "custom-top-left-corner-logo-image-url": "Веза до места на Интернету где се налази слика са Вашим логом за приказивање у горњем левом углу", + "upload-avatar": "Пошаљите Вашу сличицу на сервер", + "uploaded-avatar": "Сличица је подигнута на сервер", + "uploading-files": "Uploading files", + "upload-failed": "Upload failed", + "upload-completed": "Upload completed", + "custom-top-left-corner-logo-image-url": "Веза до места на Интернету где је слика са Вашим логом за приказивање у горњем левом углу", "custom-top-left-corner-logo-link-url": "Веза до места на Интернету које се посећује када си кликне на Ваш лого у горњем левом углу", "custom-top-left-corner-logo-height": "Висина за Ваш лого у горњем левом углу. Уобичајена вредност: 27", - "custom-login-logo-image-url": "Веза до места на Интернету са ког се повлачи насловна слика код пријаве", + "custom-login-logo-image-url": "Веза до места на Интернету где је насловна слика код пријаве", "custom-login-logo-link-url": "Слика изнад дела за пријаву води до следећег места на Интернету", "custom-help-link-url": "Место на Интернету где се може затражити помоћ", "text-below-custom-login-logo": "Порука испод насловне слике на страни за пријаву", "automatic-linked-url-schemes": "Група места на Интернету која би могла да се посете. Једна група по линији", - "username": "Кориснички налог", - "import-usernames": "Увези корисничке налоге", + "username": "Корисничко име", + "import-usernames": "Увези корисничка имена", "view-it": "Погледај је", - "warn-list-archived": "упозорење: овај предмет постоји у архиви у једном делу поступка", - "watch": "Стави на мере праћења", - "watching": "Прикупљај сва обавештења", - "watching-info": "Бићете обавештени о било каквој промени у овим списима", - "welcome-board": "Списи са добродошлицом", - "welcome-swimlane": "Врста 1", + "warn-list-archived": "упозорење: ова картица са задатком јесте у једној деоници али у архиви", + "watch": "Посматрај", + "watching": "Посматрање", + "watching-info": "Бићете обавештени о променама у овој пословној књизи", + "welcome-board": "Пословна књига за добродошлицу", + "welcome-swimlane": "Стаза 1", "welcome-list1": "Основно", "welcome-list2": "Напредно", - "card-templates-swimlane": "Обрасци за предмете", - "list-templates-swimlane": "Обрасци за делове поступака", - "board-templates-swimlane": "Обрасци за списе", + "card-templates-swimlane": "Предлошци за картице са задацима", + "list-templates-swimlane": "Предложак за деонице", + "board-templates-swimlane": "Предложак за пословну књигу", "what-to-do": "Шта желите да урадите ?", - "wipLimitErrorPopup-title": "Досегнуто ограничење броја предмета", - "wipLimitErrorPopup-dialog-pt1": "Број предмета у овом делу поступка је већи него ограничење које сте поставили.", - "wipLimitErrorPopup-dialog-pt2": "Молим да преместите неке предмете из овог дела поступка или да повећате границу.", - "admin-panel": "Управа", - "settings": "Општа поставка", + "wipLimitErrorPopup-title": "Досегнуто ограничење броја послова", + "wipLimitErrorPopup-dialog-pt1": "Број задатака на овој деоници је већи него ограничење које сте поставили.", + "wipLimitErrorPopup-dialog-pt2": "Молим да или обавите неке послове на овој деоници или да поставите веће ограничење дозвољеног броја послова.", + "admin-panel": "Главна управа", + "settings": "Подешавања", "people": "Сарадници", - "registration": "Упис", - "disable-self-registration": "Налог не може да се отвори без позивнице", - "disable-forgot-password": "Спречи процедуру за заборављену лозинку", - "invite": "Упути позив", - "invite-people": "Доле позваним омогући и пун увид", - "to-boards": "У списе", + "registration": "Отварање налога", + "disable-self-registration": "Онемогући да корисници сами себи отворе налог", + "disable-forgot-password": "Онемогући да корисници сами затраже промену лозинке", + "invite": "Позив", + "invite-people": "Позови сараднике", + "to-boards": "У пословну књигу(e)", "email-addresses": "Адресе е-поште", "smtp-host-description": "Тачна адреса SMTP сервера који рукује вашом е-поштом.", "smtp-port-description": "Порт Вашег SMTP сервера који се користи за одлазну е-пошту.", @@ -720,23 +686,23 @@ "smtp-password": "Лозинка", "smtp-tls": "TLS подршка", "send-from": "Од", - "send-smtp-test": "Проба да ли поруке пролазе", + "send-smtp-test": "Пошаљите сами себи једну пробну е-поруку", "invitation-code": "Позивни код", "email-invite-register-subject": "__inviter__ Вам је послао позивницу", - "email-invite-register-text": "Поштовани __user__,\n\n__inviter__ Вас позива да користите kanban списе за заједнички рад.\n\nМолим испратите везу испод:\n__url__\n\nИ да, Ваш позивни код је: __icode__\n\nХвала.", + "email-invite-register-text": "Драги __user__,\n\n__inviter__ Вас позива да користите kanban пословне књиге за заједнички рад.\n\nМолим испратите везу испод:\n__url__\n\nИ да, Ваш позивни код је: __icode__\n\nХвала.", "email-smtp-test-subject": "Проба да ли ради слање е-поште", "email-smtp-test-text": "Успешно сте послали е-пошту", - "error-invitation-code-not-exist": "Такав позивни код не постоји", + "error-invitation-code-not-exist": "Позивни код не постоји", "error-notAuthorized": "Немате права да приступате овој страници.", - "webhook-title": "Назив за дојаву догађаја", + "webhook-title": "Назив мрежне куке", "webhook-token": "Token (необавезно за проверу идентитета)", - "outgoing-webhooks": "Дојава догађаја", - "bidirectional-webhooks": "Двосмерно понашање", - "outgoingWebhooksPopup-title": "Дојава догађаја", + "outgoing-webhooks": "Одлазна мрежна кука", + "bidirectional-webhooks": "Двосмерне мрежне куке", + "outgoingWebhooksPopup-title": "Одлазне мрежне куке", "boardCardTitlePopup-title": "Издвајање по наслову задатка", - "disable-webhook": "Онеспособи ову дојаву догађаја", - "global-webhook": "Дојава догађаја", - "new-outgoing-webhook": "Нова дојава догађаја", + "disable-webhook": "Онеспособи ову мрежну куку", + "global-webhook": "Глобална мрежна кука", + "new-outgoing-webhook": "Нова одлазна мрежна кука", "no-name": "(непознато)", "Node_version": "Node верзија", "Meteor_version": "Meteor верзија", @@ -756,81 +722,78 @@ "hours": "сати", "minutes": "минута", "seconds": "секунди", - "show-field-on-card": "Придружи ову рубрику овом предмету", - "automatically-field-on-card": "Сви будући предмети ће имати овакву рубрику", - "always-field-on-card": "Придодај овакву рубрику и свим постојећим предметима", - "showLabel-field-on-card": "Прикажи натпис ове рубрике на омоту предмета", - "showSum-field-on-list": "Прикажи збирни број придружених рубрика на врху деонице", + "show-field-on-card": "Прикажи ово поље на картицу са задатком", + "automatically-field-on-card": "Придодај поље новим картицама", + "always-field-on-card": "Придодај поље свим картицама", + "showLabel-field-on-card": "Прикажи натпис на налепници", + "showSum-field-on-list": "Прикажи збирни број поља на врху деонице", "yes": "Да", "no": "Не", "accounts": "Налози", "accounts-allowEmailChange": "Дозволи промену адресе е-поште", - "accounts-allowUserNameChange": "Дозволи промену имена налога", - "tableVisibilityMode-allowPrivateOnly": "Тајност списа: Дозволи приступ искључиво затвореном кругу сарадника", - "tableVisibilityMode": "Тајност списа", + "accounts-allowUserNameChange": "Дозволи промену корисничког имена", + "tableVisibilityMode-allowPrivateOnly": "Видљивост пословне књиге: Дозволи једино приватне пословне књиге", + "tableVisibilityMode" : "Видљивост пословне књиге", "createdAt": "Направљено дана", "modifiedAt": "Време измене", - "verified": "Потврђен налог", + "verified": "Потрврђено", "active": "На окупу", - "card-received": "Запримљен", - "card-received-on": "Предмет запримљен дана", - "card-end": "Окончан", - "card-end-on": "Предмет окончан дана", - "editCardReceivedDatePopup-title": "Датум и време запримања предмета", - "editCardEndDatePopup-title": "Кад је задатак окончан", - "setCardColorPopup-title": "Боја омота предмета", - "setSelectionColorPopup-title": "Боја за изабране ставке", - "setCardActionsColorPopup-title": "Боја за подлогу предмета", - "setSwimlaneColorPopup-title": "Боја за врсту поступка", - "setListColorPopup-title": "Боја за део поступка", - "assigned-by": "Властодавац", - "requested-by": "Налогодавац", - "card-sorting-by-number": "Бројчана ознака за редослед", - "board-delete-notice": "Избацивање је трајно. Изгубићете све делове поступка, предмете и радње повезане са овим списима.", - "delete-board-confirm-popup": "Сви делови поступка, предмети, налепнице и записници биће избачени и нећете моћи да повратите садржај списа. Опозив ове радње неће бити могућ.", - "boardDeletePopup-title": "Избацићете ове списе?", - "delete-board": "Избаци списе", - "delete-all-notifications": "Избриши сва", - "delete-all-notifications-confirm": "Да ли сте сигурни да желите да избришете сва обавештења? Опозив ове радње неће бити могућ.", - "delete-duplicate-lists": "Уклоните истоимене делове поступка", - "delete-duplicate-lists-confirm": "Да ли сте сигурни? Овом радњом ће бити избрисани сви истоимени делови поступка где нема предмета.", - "default-subtasks-board": "Утврђени пријемни списи __board__", + "card-received": "Пријем", + "card-received-on": "Примљен дана", + "card-end": "Завршетак", + "card-end-on": "Завршава се дана", + "editCardReceivedDatePopup-title": "Датум и време запримања задатка", + "editCardEndDatePopup-title": "Кад је задатак постао испуњен", + "setCardColorPopup-title": "Обоји картицу", + "setCardActionsColorPopup-title": "Изабери боју", + "setSwimlaneColorPopup-title": "Изабери боју стазе", + "setListColorPopup-title": "Изабери боју деонице", + "assigned-by": "Додељено од стране", + "requested-by": "Захтевано од", + "card-sorting-by-number": "Пресложи картице по броју", + "board-delete-notice": "Избацивање је трајно. Изгубићете све деонице, картице са задацима и радње повезане са овом књигом пословања.", + "delete-board-confirm-popup": "Све деонице, картице са задацима, налепнице и радње биће избачене и нећете моћи да повратите садржај књиге пословања. Опозив ове радње неће бити могућ.", + "boardDeletePopup-title": "Избацићете пословну књигу?", + "delete-board": "Избаци пословну књигу", + "delete-duplicate-lists": "Delete Duplicate Lists", + "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", + "default-subtasks-board": "Под задаци за __board__ књигу пословања", "default": "Подразумевано", "defaultdefault": "Подразумевано", - "queue": "Пријемно одељење", - "subtask-settings": "Поставка издвојених послова", - "card-settings": "Појединости и омот предмета", - "minicard-settings": "Рад са омотима предмета", - "boardSubtaskSettingsPopup-title": "Руковање издвојеним пословима", - "boardCardSettingsPopup-title": "Предмети у списима", - "boardMinicardSettingsPopup-title": "Рад са омотима предмета", - "deposit-subtasks-board": "Депонуј издвојене послове у списе:", - "deposit-subtasks-list": "Пријемно одељење кад овде депонују:", - "show-parent-in-minicard": "Испис порекла на омоту предмета:", - "description-on-minicard": "Пун опис", - "cover-attachment-on-minicard": "Мотив из предметне грађе", - "badge-attachment-on-minicard": "Број поред спајалице", - "card-sorting-by-number-on-minicard": "Бројчана ознака за редослед", - "prefix-with-full-path": "Испиши пуну путању као уводни слог", - "prefix-with-parent": "Испиши порекло као уводни слог", - "subtext-with-full-path": "Испиши пуну путању испод ситним словима", - "subtext-with-parent": "Испиши порекло испод ситним словима", - "change-card-parent": "Промени порекло предмета", - "parent-card": "Родитељски предмет", - "source-board": "Изворни списи", - "no-parent": "Немој исписивати никакво порекло", + "queue": "Ред", + "subtask-settings": "Подешавања подзадатака", + "card-settings": "Подешавања картице са задацима", + "minicard-settings": "Подешавања малих картица", + "boardSubtaskSettingsPopup-title": "Подешавања подзадатака", + "boardCardSettingsPopup-title": "Подешавања картица са задацима", + "boardMinicardSettingsPopup-title": "Подешавања малих картица", + "deposit-subtasks-board": "Депонуј подзадатке у ову пословну књигу:", + "deposit-subtasks-list": "Деоница у којој се смештају под задаци овде депоновани:", + "show-parent-in-minicard": "Прикажи порекло у малој картици:", + "description-on-minicard": "Опис мале картице", + "cover-attachment-on-minicard": "Насловна слика на мини картици", + "badge-attachment-on-minicard": "Број прилога на мини картици", + "card-sorting-by-number-on-minicard": "Слагање картица по броју на мини картици", + "prefix-with-full-path": "Префикс са пуном путањом", + "prefix-with-parent": "Префикс са пореклом", + "subtext-with-full-path": "Subtext са пуном путањом", + "subtext-with-parent": "Subtext са пореклом", + "change-card-parent": "Промени порекло картице", + "parent-card": "Родитељска картица", + "source-board": "Изворна књига пословања", + "no-parent": "Не приказуј порекло", "activity-added-label": "залепљена налепница '%s' на %s", "activity-removed-label": "уклоњена је налепница '%s' са %s", "activity-delete-attach": "уклоњен је прилог са %s", "activity-added-label-card": "залепљена је налепница '%s'", "activity-removed-label-card": "уклоњена је налепница '%s'", "activity-delete-attach-card": "избрисан је прилог", - "activity-set-customfield": "придружена је рубрика '%s' на '%s' у %s", - "activity-unset-customfield": "уклоњена је рубрика '%s' у %s", + "activity-set-customfield": "постављено је сасвим ново поље '%s' на '%s' у %s", + "activity-unset-customfield": "уклоњено је сасвим ново поље '%s' у %s", "r-rule": "Правило", - "r-add-trigger": "Прво додајте догађај", - "r-add-action": "Затим додајте радњу као одговор", - "r-board-rules": "Списак правила", + "r-add-trigger": "Додај окидач", + "r-add-action": "Додај радњу", + "r-board-rules": "Правила пословне књиге", "r-add-rule": "Додај правило", "r-view-rule": "Прегледај правило", "r-delete-rule": "Обриши правило", @@ -838,149 +801,140 @@ "r-no-rules": "Није уведено правило", "r-trigger": "Окидач", "r-action": "Радња", - "r-when-a-card": "Предмет", + "r-when-a-card": "Када картица", "r-is": "је", - "r-is-moved": "премештен", - "r-added-to": "додана на", - "r-removed-from": "скинут са", - "r-the-board": "списа", - "r-list": "део", + "r-is-moved": "је премештена", + "r-added-to": "Додан у", + "r-removed-from": "Уклоњена из", + "r-the-board": "пословна књига", + "r-list": "деоница", "set-filter": "Правило издвајања", - "r-moved-to": "премештен у", - "r-moved-from": "премештен из", + "r-moved-to": "Премештен у", + "r-moved-from": "Премештен из", "r-archived": "Премештен у архиву", "r-unarchived": "Извучен из архиве", - "r-a-card": "предмет", - "r-when-a-label-is": "Када је било која налепница", - "r-when-the-label": "Налепница", - "r-list-name": "име дела поступка", - "r-when-a-member": "Када је било који сарадник", - "r-when-the-member": "Сарадник по имену", + "r-a-card": "једна картица са задатком", + "r-when-a-label-is": "Када је налепница", + "r-when-the-label": "Када налепница", + "r-list-name": "име деонице", + "r-when-a-member": "Када је сарадник", + "r-when-the-member": "Када сарадник", "r-name": "име", - "r-when-a-attach": "Када је нека предметна грађа", - "r-when-a-checklist": "Када је нека предметна радња", - "r-when-the-checklist": "Када је предметна радња", - "r-completed": "обављена", - "r-made-incomplete": "означена као необављена", - "r-when-a-item": "Када је нека ставка са списка", - "r-when-the-item": "Када је ставка са списка", - "r-checked": "прецртана", - "r-unchecked": "означена као не прецртана", - "r-move-card-to": "Премести предмет на", + "r-when-a-attach": "Када је додатак", + "r-when-a-checklist": "Када је списак за обавити", + "r-when-the-checklist": "Када списак за обавити", + "r-completed": "Обављен", + "r-made-incomplete": "Није обављен", + "r-when-a-item": "Када је ставка на списку за обавити", + "r-when-the-item": "Када ставка на списку за обавити", + "r-checked": "Обављена", + "r-unchecked": "Необављена", + "r-move-card-to": "Премести картицу са задатком на", "r-top-of": "Врх", "r-bottom-of": "Дно", - "r-its-list": "свог дела поступка", - "r-archive": "спакован у архиву", - "r-unarchive": "извучен из архиве", - "r-card": "предмет", + "r-its-list": "своје деонице", + "r-archive": "Премести у архиву", + "r-unarchive": "Извуци из архиве", + "r-card": "картица", "r-add": "Додај", "r-remove": "Уклони", - "r-label": "налепницу", - "r-member": "сарадника по имену", - "r-remove-all": "Скини све сареднике са предмета", - "r-set-color": "Обоји", - "r-checklist": "предметну радњу", - "r-check-all": "Прецртај све", - "r-uncheck-all": "Означи све као непрецртано", - "r-items-check": "са списка", - "r-check": "Прецртај", - "r-uncheck": "Врати на дораду", - "r-item": "ставку", - "r-of-checklist": "са списка", + "r-label": "налепница", + "r-member": "сарадник", + "r-remove-all": "Удаљи све сараднике са задатка", + "r-set-color": "Обоји са", + "r-checklist": "списак за обавити", + "r-check-all": "Све је обављено", + "r-uncheck-all": "Није ништа обављено", + "r-items-check": "ставке на списку за обавити", + "r-check": "Обављено", + "r-uncheck": "Необављено", + "r-item": "ставка", + "r-of-checklist": "списка за обавити", "r-send-email": "Пошаљи е-пошту", "r-to": "за", "r-of": "од", "r-subject": "наслов", "r-rule-details": "Детаљи извођења правила", - "r-d-move-to-top-gen": "Помери предмет на врх тог дела поступка", - "r-d-move-to-top-spec": "Помери предмет на врх дела поступка", - "r-d-move-to-bottom-gen": "Помери предмет на дно тог дела поступка", - "r-d-move-to-bottom-spec": "Помери предмет на дно дела поступка", + "r-d-move-to-top-gen": "Премести картицу са задатком на врх своје деонице", + "r-d-move-to-top-spec": "Премести картицу са задатком на врх деонице", + "r-d-move-to-bottom-gen": "Премести картицу са задатком на дно своје деонице", + "r-d-move-to-bottom-spec": "Премести картицу са задатком на дно деонице", "r-d-send-email": "Пошаљи е-пошту", "r-d-send-email-to": "за", "r-d-send-email-subject": "наслов", "r-d-send-email-message": "порука", - "r-d-archive": "Премести предмет у архиву", - "r-d-unarchive": "Извуци предмет из архиве", + "r-d-archive": "Премести картицу са задатком у архиву", + "r-d-unarchive": "Извуци из архиве картицу са задатком", "r-d-add-label": "Залепи налепницу", "r-d-remove-label": "Уклони налепницу", - "r-create-card": "Заведи предмет", - "r-in-list": "у делу", - "r-in-swimlane": "поступка", + "r-create-card": "Направи нову картицу са задатком", + "r-in-list": "на деоници", + "r-in-swimlane": "у стази", "r-d-add-member": "Прими сарадника", - "r-d-remove-member": "Изузми сарадника", - "r-d-remove-all-member": "Изузми све сараднике", - "r-d-check-all": "Означи све ставке са списка као обављене", - "r-d-uncheck-all": "Означи све ставке са списка као необављене", - "r-d-check-one": "Обављена радња", - "r-d-uncheck-one": "Необављена радња", + "r-d-remove-member": "Удаљи сарадника", + "r-d-remove-all-member": "Удаљи све сараднике", + "r-d-check-all": "Означи све ставке на списку за обавити као обављене", + "r-d-uncheck-all": "Означи све ставке на списку за обавити као необављене", + "r-d-check-one": "Обављена ставка", + "r-d-uncheck-one": "Необављена ставка", "r-d-check-of-list": "списка за обавити", - "r-d-add-checklist": "Додај списак", - "r-d-remove-checklist": "Уклони списак", + "r-d-add-checklist": "Додај списак за обавити", + "r-d-remove-checklist": "Уклони списак за обавити", "r-by": "од", - "r-add-checklist": "Кад неко дода списак", + "r-add-checklist": "Додај списак за обавити", "r-with-items": "са ставкама", "r-items-list": "ставка1,ставка2,ставка3", - "r-add-swimlane": "Додај врсту поступка", - "r-swimlane-name": "врсту поступка", - "r-board-note": "Белешка: Непопуњено поље одговара свакој могућој вредности.", - "r-checklist-note": "Белешка: Ставке на списку морају да буду раздвојене зарезом.", - "r-when-a-card-is-moved": "Када је предмет премештен у други део поступка", - "r-set": "Унеси", - "r-update": "Помери", - "r-datefield": "временску одредницу кад је предмет", - "r-df-start-at": "започет", - "r-df-due-at": "орочен", - "r-df-end-at": "окончан", - "r-df-received-at": "запримљен", - "r-to-current-datetime": "на данашњи датум и тренутно време", - "r-remove-value-from": "Уклони временску одредницу из поља", - "r-link-card": "Кад је предмет у вези са списима", + "r-add-swimlane": "Додај стазу", + "r-swimlane-name": "име стазе", + "r-board-note": "Белешка: Остављено празно поље одговара свакој могућој вредности.", + "r-checklist-note": "Белешка: Ставке на списку за обавити морају да буду раздвојене зарезом.", + "r-when-a-card-is-moved": "Када је картица са задатком премештена на другу деоницу", + "r-set": "Постави", + "r-update": "Освежи", + "r-datefield": "поље са датумом", + "r-df-start-at": "почетак", + "r-df-due-at": "крајњи датум", + "r-df-end-at": "крај", + "r-df-received-at": "примљен", + "r-to-current-datetime": "до тренутног датума/времена", + "r-remove-value-from": "Уклони вредност са", + "r-link-card": "Увежи картицу на", "ldap": "LDAP", "oauth2": "OAuth2", "cas": "CAS", "authentication-method": "Метод утврђивања идентитета", "authentication-type": "Врста утврђивања идентитета", "custom-product-name": "Ваша реклама", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Распоред", "hide-logo": "Сакриј лого", - "hide-card-counter-list": "Сакриј бројач предмета на полици са списима", - "hide-board-member-list": "Сакриј списак сарадника на полици са списима", - "add-custom-html-after-body-start": "Уметни овај HTML код где почиње <body> деоница", - "add-custom-html-before-body-end": "Уметни овај HTML код пред крај </body> деонице", - "error-undefined": "Имамо непознат проблем", + "hide-card-counter-list": "Сакриј бројач задатака на картици у свим пословним књигама", + "hide-board-member-list": "Сакриј списак сарадника у свим књигама пословања", + "add-custom-html-after-body-start": "Уметни овај HTML код на сваки почетак <body> деонице", + "add-custom-html-before-body-end": "Уметни овај HTML код пред сваки крај </body> деонице", + "error-undefined": "Негде је настао проблем", "error-ldap-login": "Догодила се нека грешка приликом покушаја пријављивања", "display-authentication-method": "Прикажи метод за утврђивање идентитета", "oidc-button-text": "Измени OIDC текст на дугмету", "default-authentication-method": "Уобичајени метод за утврђивање идентитета", - "duplicate-board": "Умножи списе у још један примерак", + "duplicate-board": "Умножи пословну књигу у још један примерак", "duplicate-board-confirm": "Are you sure you want to duplicate this board?", - "org-number": "Број евидентираних странака је:", - "team-number": "Број правних тимова је:", - "people-number": "Укупан број сарадника је:", - "swimlaneDeletePopup-title": "Избрисаћете овај поступак ?", - "swimlane-delete-pop": "Све радње ће бити уклоњене из записника и нећете моћи да опоравите поступак. Нема поништавања.", + "org-number": "Број предузећа је:", + "team-number": "Број тимова је:", + "people-number": "Број сарадника је:", + "swimlaneDeletePopup-title": "Избрисаћете стазу ?", + "swimlane-delete-pop": "Све радње ће бити уклоњене из активности и нећете моћи да вратите стазу. Нема поништавања.", "restore-all": "Опорави све", "delete-all": "Обриши све", "loading": "Исчитавање, молим сачекајте.", "previous_as": "прошли пут је био", - "act-a-dueAt": "измењени крајњи рок \nРок:__timeValue__\nПредмет:__card__\nПретходни крајњи рок је био __timeOldValue__", - "act-a-endAt": "измењен датум и време окончања на __timeValue__ са претходно постављеног (__timeOldValue__)", - "act-a-startAt": "измењен датум и време почетка рада на предмету на __timeValue__ са претходно постављеног (__timeOldValue__)", - "act-a-receivedAt": "измењен датум и време пријема предмета на __timeValue__ са претходно постављеног (__timeOldValue__)", + "act-a-dueAt": "измењени крајњи рок \nКада:__timeValue__\nГде:__card__\nПретходни рок је био __timeOldValue__", + "act-a-endAt": "измењено време завршетка на __timeValue__ са претходно постављеног (__timeOldValue__)", + "act-a-startAt": "измењено време почетка на __timeValue__ са претходно постављеног (__timeOldValue__)", + "act-a-receivedAt": "измењено време пријема на __timeValue__ са претходно постављеног (__timeOldValue__)", "a-dueAt": "измењен крајњи рок", "a-endAt": "измењено време завршетка", "a-startAt": "измењено време почетка", "a-receivedAt": "измењено време пријема", - "above-selected-card": "Изнад изабраног предмета", - "below-selected-card": "Испод изабраног предмета", "almostdue": "приближава се крајњи рок %s", "pastdue": "крајњи рок %s је пробијен", "duenow": "крајњи рок %s је данас", @@ -989,32 +943,31 @@ "act-almostdue": "подсећам да се приближавамо последњем року (__timeValue__) за завршетак задатка __card__", "act-pastdue": "подсећам да је последњи рок (__timeValue__) задатка __card__ пробијен", "act-duenow": "подсећам да последњи рок (__timeValue__) задатка __card__ истиче данас", - "act-atUserComment": "споменути сте у расправи у предмету __card__: __comment__ у делу __list__ поступка __swimlane__ у списима __board__", - "delete-user-confirm-popup": "Да ли сте сигурни да желите да уклоните овај појединачни кориснички налог сарадника? Опозив ове радње неће бити могућ.", - "delete-team-confirm-popup": "Да ли сте сигурни да желите да уклоните овај цео правни тим? Опозив ове радње неће бити могућ.", - "delete-org-confirm-popup": "Да ли сте сигурни да желите да уклоните ову странку? Опозив ове радње неће бити могућ.", - "accounts-allowUserDelete": "Дозволи сарадницима да сами испишу свој налог из јединственог регистра", + "act-atUserComment": "Споменути сте у пословној књизи [__board__] тачније на деоници __list__ на картици са задатком __card__", + "delete-user-confirm-popup": "Да ли сте сигурни да желите да избришете овај кориснички налог? Опозив ове радње неће бити могућ.", + "delete-team-confirm-popup": "Да ли сте сигурни да желите да избришете овај тим? Опозив ове радње неће бити могућ.", + "delete-org-confirm-popup": "Да ли сте сигурни да желите да избришете ово предузеће? Опозив ове радње неће бити могућ.", + "accounts-allowUserDelete": "Дозволи корисницима да сами избришу свој налог", "hide-minicard-label-text": "Сакриј натпис на налепници", "show-desktop-drag-handles": "Прикажи ручке за повлачење на радној површини", - "assignee": "Пуномоћник", - "cardAssigneesPopup-title": "Коме се даје пуномоћ", + "assignee": "Извршилац", + "cardAssigneesPopup-title": "Коме се додељује да изврши", "addmore-detail": "Додај детаљнији опис", - "show-on-card": "Видљиво у појединостима", - "show-on-minicard": "Видљиво на омоту", + "show-on-card": "Прикажи на картици", + "show-on-minicard": "Прикажи на налепници", "new": "Ново", - "editOrgPopup-title": "Опис странке", - "newOrgPopup-title": "Нова странка", - "editTeamPopup-title": "Опис правног тима", - "newTeamPopup-title": "Нови правни тим", - "editUserPopup-title": "Опис сарадника", - "newUserPopup-title": "Нови сарадник", + "editOrgPopup-title": "Уреди предузеће", + "newOrgPopup-title": "Ново предузеће", + "editTeamPopup-title": "Уреди податке за тим", + "newTeamPopup-title": "Нови тим", + "editUserPopup-title": "Уреди податке корисника", + "newUserPopup-title": "Нови корисник", "notifications": "Обавештења", "help": "Помоћ", "view-all": "Прикажи сва", "filter-by-unread": "Издвој непрочитана", - "mark-all-as-read": "Означи сва као прочитана", - "mark-all-as-unread": "Означи сва као непрочитана", - "remove-all-read": "Избриши сва прочитана", + "mark-all-as-read": "Означи све као прочитано", + "remove-all-read": "Склони све прочитано", "allow-rename": "Дозволи преименовање", "allowRenamePopup-title": "Дозволи преименовање", "start-day-of-week": "Постави који дан се рачуна за почетак недеље", @@ -1026,113 +979,109 @@ "saturday": "Субота", "sunday": "Недеља", "status": "Стање", - "swimlane": "Поступак", + "swimlane": "Стаза", "owner": "Власник", "last-modified-at": "Време последње измене", "last-activity": "Последње промене", "voting": "Гласање", "archived": "Архивирано", - "delete-linked-card-before-this-card": "Можете избрисати овај предмет тек кад, прво, избришете предмет у вези са њим", - "delete-linked-cards-before-this-list": "Не можете избрисати овај део поступка - прво избришете предмете који су у вези са предметима у овом делу поступка", - "hide-checked-items": "Сакриј обављене помоћне предметне радње", - "hide-finished-checklist": "Сакриј обављене предметне радње", + "delete-linked-card-before-this-card": "Не можете избрисати ову картицу пре него што прво избришете повезану картицу која има", + "delete-linked-cards-before-this-list": "Не можете избрисати ову деоницу -- прво избришете повезане картице које показују на картице на овој деоници", + "hide-checked-items": "Сакриј већ обављено са списка", + "hide-finished-checklist": "Hide finished checklist", "task": "Задатак", "create-task": "Задај", - "ok": "Сагласан", - "organizations": "Странке", - "teams": "Правни тимови", - "displayName": "Име (Пословно име)", - "shortName": "Скраћено пословно име", - "autoAddUsersWithDomainName": "Повежи са оним сарадницима чији је домен е-поште", - "website": "Званична интернет страница", - "person": "Лице", - "my-cards": "Предмети где имам надлежност", - "card": "Предмет", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", - "list": "Део поступка", - "board": "Списи", + "ok": "У реду", + "organizations": "Предузећа", + "teams": "Тимови", + "displayName": "Име које се приказује", + "shortName": "Скраћеница", + "autoAddUsersWithDomainName": "Automatically add users with the domain name", + "website": "Интернет страница", + "person": "Особа", + "my-cards": "Моја задужења", + "card": "Картица", + "list": "Деоница", + "board": "Пословна књига", "context-separator": "/", - "myCardsViewChange-title": "Поглед на такве предмете", - "myCardsViewChangePopup-title": "Такви предмети треба да буду", - "myCardsViewChange-choice-boards": "Размештени по списима", - "myCardsViewChange-choice-table": "Приказани у табели", - "myCardsSortChange-title": "Редослед предмета где имам пуномоћ", - "myCardsSortChangePopup-title": "Поређај предмете где имам пуномоћ", - "myCardsSortChange-choice-board": "По деловодном броју списа", + "myCardsViewChange-title": "Приказ мојих задужења", + "myCardsViewChangePopup-title": "Приказ мојих задужења", + "myCardsViewChange-choice-boards": "Размештена по пословним књигама", + "myCardsViewChange-choice-table": "Приказана у табели", + "myCardsSortChange-title": "Издвајање мојих личних картица", + "myCardsSortChangePopup-title": "Издвајање мојих личних картица", + "myCardsSortChange-choice-board": "По натпису на књизи пословања", "myCardsSortChange-choice-dueat": "По датуму доспећа", - "dueCards-title": "Предмети са роковима", - "dueCardsViewChange-title": "Надлежност", - "dueCardsViewChangePopup-title": "Надлежност", - "dueCardsViewChange-choice-me": "где имам пуномоћ", - "dueCardsViewChange-choice-all": "где имам право увида", - "dueCardsViewChange-choice-all-description": "Приказује све нерешене предмете који имају постављен рок решавања из оних списа за које корисник има одобрење.", - "dueCards-noResults-title": "Није пронађен ниједан предмет са роком", - "dueCards-noResults-description": "Тренутно немате никакве предмете који има постављене рокове.", - "broken-cards": "Предмети где је веза покидана", - "board-title-not-found": "Списи под деловодним бројем '%s' нису пронађени.", - "swimlane-title-not-found": "Ток поступка '%s' није пронађен.", - "list-title-not-found": "Део поступка '%s' није пронађен.", - "label-not-found": "Натпис на налепници '%s' није пронађен.", - "label-color-not-found": "%s налепница у боји није пронађена.", - "user-username-not-found": "Сарадник са корисничким налогом '%s' није пронађен.", + "dueCards-title": "Послови којима истиче рок", + "dueCardsViewChange-title": "Приказ картица које истичу", + "dueCardsViewChangePopup-title": "Приказ картица које истичу", + "dueCardsViewChange-choice-me": "Моји недовршени послови", + "dueCardsViewChange-choice-all": "Недовршени послови сарадника", + "dueCardsViewChange-choice-all-description": "Прикажи све недовршене задатке који имају постављен рок решавања из пословних књига за које корисник има одобрење.", + "dueCards-noResults-title": "No Due Cards Found", + "dueCards-noResults-description": "You don't have any cards with due dates at the moment.", + "broken-cards": "Покидане картице", + "board-title-not-found": "Књига пословања '%s' није пронађена.", + "swimlane-title-not-found": "Стаза '%s' није пронађена.", + "list-title-not-found": "Деоница '%s' није пронађена.", + "label-not-found": "Налепница '%s' није пронађена.", + "label-color-not-found": "Боја налепнице %s није пронађена.", + "user-username-not-found": "Корисничко име '%s' није пронађено.", "comment-not-found": "Мишљење које садржи речи '%s' није пронађено.", - "org-name-not-found": "Странка по имену '%s' није пронађена.", - "team-name-not-found": "Правни тим по имену '%s' није пронађен.", - "globalSearch-title": "Претрага по списима", - "no-cards-found": "Није пронађен ни један предмет", - "one-card-found": "Пронађен је један предмет", - "n-cards-found": "Број пронађених предмета: %s", - "n-n-of-n-cards-found": "__start__-__end__ од __total__ предмета је нађено", - "operator-board": "списи", - "operator-board-abbrev": "с", - "operator-swimlane": "поступак", - "operator-swimlane-abbrev": "п", - "operator-list": "део", + "org-name-not-found": "Предузеће '%s' није пронађено.", + "team-name-not-found": "Тим '%s' није пронађен.", + "globalSearch-title": "Претрага", + "no-cards-found": "Није пронађена ни једна картица са задацима", + "one-card-found": "Пронађена је једна картица са задацима", + "n-cards-found": "Број пронађених картица са задацима: %s", + "n-n-of-n-cards-found": "__start__-__end__ од __total__ картица је нађено", + "operator-board": "пословна књига", + "operator-board-abbrev": "п", + "operator-swimlane": "стаза", + "operator-swimlane-abbrev": "с", + "operator-list": "деонице", "operator-list-abbrev": "д", - "operator-label": "налепницa", + "operator-label": "налепнице", "operator-label-abbrev": "#", "operator-user": "корисник", "operator-user-abbrev": "@", "operator-member": "сарадник", "operator-member-abbrev": "н", - "operator-assignee": "пуномоћник", - "operator-assignee-abbrev": "м", - "operator-creator": "завео", + "operator-assignee": "коме је додељено", + "operator-assignee-abbrev": "о", + "operator-creator": "налогодавац", "operator-status": "стање", "operator-due": "крајњи датум", - "operator-created": "заведено", + "operator-created": "постављен", "operator-modified": "измењено", "operator-sort": "редослед", "operator-comment": "мишљење", "operator-has": "има", "operator-limit": "ограничење", "operator-debug": "тражење грешака", - "operator-org": "странка", + "operator-org": "предузеће", "operator-team": "тим", "predicate-archived": "архивиран", "predicate-open": "отворен", "predicate-ended": "окончан", "predicate-all": "све", "predicate-overdue": "истекао", - "predicate-week": "недељно", - "predicate-month": "месечно", - "predicate-quarter": "тромесечно", - "predicate-year": "годишње", + "predicate-week": "недеља", + "predicate-month": "месец", + "predicate-quarter": "тромесечје", + "predicate-year": "година", "predicate-due": "рок", - "predicate-modified": "изменио", - "predicate-created": "завео", - "predicate-attachment": "приложио", - "predicate-description": "описао", - "predicate-checklist": "предметна радња", - "predicate-start": "започео", - "predicate-end": "окончао", - "predicate-assignee": "овластио", + "predicate-modified": "измењен", + "predicate-created": "направљен", + "predicate-attachment": "прилог", + "predicate-description": "опис", + "predicate-checklist": "списак за обавити", + "predicate-start": "почетак", + "predicate-end": "крај", + "predicate-assignee": "задужени", "predicate-member": "сарадник", - "predicate-public": "јавни", - "predicate-private": "тајни", + "predicate-public": "јавно", + "predicate-private": "приватно", "predicate-selector": "изборник", "predicate-projection": "очекивање", "operator-unknown-error": "%s уопште није оператор", @@ -1144,76 +1093,76 @@ "operator-debug-invalid": "%s уопште није исправан предикат", "next-page": "Следећа страна", "previous-page": "Претходна страна", - "heading-notes": "Додатак", - "globalSearch-instructions-heading": "Правилник за детаљно претраживање", - "globalSearch-instructions-description": "Да би сте сузили резултате претраге предлажемо Вам да почнете да користите операнде. Операнд је математички појам којег овде дефинишемо као целину која се састоји од променљиве и њене припадајуће вредности који су међусобно раздвојени двотачком. На пример, ако задате операнд `део:одложено` то ће сузити претрагу само на оне предмете који су смештени у делу поступка под називом *одложено*. Уколико вредност садржи размак између речи или садржи посебне знакове вредност морате обухватити наводницима (нпр. `__operator_list__:\"у жалбеном поступку\"`).", + "heading-notes": "Белешке", + "globalSearch-instructions-heading": "Упутство за детаљно претраживање", + "globalSearch-instructions-description": "Да би се претрага побољшала, можете да користите операторе. Оператори се задају навођењем имена оператора и навођењем вредности где се оператор и вредност раздвајају двотачком. На пример, ако задате `деоница:блокирано` то ће сузити претрагу само на оне картице са задацима које су смештене на деоници под називом *блокирано*. Уколико вредност садржи празна места или посебне знакове морате их обухватити наводницима (нпр. `__operator_list__:\"радови у току\"`).", "globalSearch-instructions-operators": "Оператори на располагању:", - "globalSearch-instructions-operator-board": "`__operator_board__:<наслов>` - сви предмети у списима са задатим *<насловом>*", - "globalSearch-instructions-operator-list": "`__operator_list__:<назив>` - сви предмети чији део поступка има следећи *<назив>*", - "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<врста>` - сви предмети који се налазе у задатој *<врсти>* поступка", - "globalSearch-instructions-operator-comment": "`__operator_comment__:<речи>` - сви предмети где су у расправи коришћене следеће *<речи>*.", - "globalSearch-instructions-operator-label": "`__operator_label__:<боја>` `__operator_label__:<натпис>` - сви предмети на које је залепљена налепница следеће *<боје>* а која има *<натпис>", - "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<име|боја>` - скраћеница за `__operator_label__:<боја>` или `__operator_label__:<име>`", - "globalSearch-instructions-operator-user": "`__operator_user__:<неко>` - сви предмети где је *<неко>* или *сарадник* или *пуномоћник*", + "globalSearch-instructions-operator-board": "`__operator_board__:<наслов>` - задаци на картицама у пословним књигама које садрже *<наслов>*", + "globalSearch-instructions-operator-list": "`__operator_list__:<назив>` - задаци на деоницама које садрже *<назив>*", + "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<назив>` - задаци на стазама које садрже *<назив>*", + "globalSearch-instructions-operator-comment": "`__operator_comment__:<речи>` - задаци где је мишљење исказано *<речима>*.", + "globalSearch-instructions-operator-label": "`__operator_label__:<боја>` `__operator_label__:<име>` - задаци чија налепница има *<боју>* or *<име>", + "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<име|боја>` - скраћеница за `__operator_label__:<боју>` или `__operator_label__:<име>`", + "globalSearch-instructions-operator-user": "`__operator_user__:<неко>` - задаци где је *<неко>* или *сарадник* или *извршилац*", "globalSearch-instructions-operator-at": "`__operator_user_abbrev__корисничко име` - скраћеница за `user:<корисничко име>`", - "globalSearch-instructions-operator-member": "`__operator_member__:<неко>` - предмети где је *<неко>* сарадник", - "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<неко>` - предмети где је *<неко>* пуномоћник", - "globalSearch-instructions-operator-creator": "`__operator_creator__:<неко>` - предмети које је завео *<неко>*", - "globalSearch-instructions-operator-org": "`__operator_org__:<име или пословно име|скраћено пословно име>` - предмети у вези са *<странком>*", - "globalSearch-instructions-operator-team": "`__operator_team__:<назив тима|скраћени назив>` - предмети које обрађује правни тим *<назив>*", - "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - предмети чији рок истиче за *<n>* дана. `__operator_due__:__predicate_overdue__ предмети са истеклим роком.", - "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - предмети који су заведени у задњих *<n>* дана", - "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - предмети који су имали измене у задњих *<n>* дана", + "globalSearch-instructions-operator-member": "`__operator_member__:<неко>` - задаци где је *<неко>* *сарадник*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<неко>` - задаци где је *<неком>* *додељен* задатак", + "globalSearch-instructions-operator-creator": "`__operator_creator__:<неко>` - задаци где је *<неко>* направио картицу са задатком", + "globalSearch-instructions-operator-org": "`__operator_org__:<назив предузећа|скраћени назив>` - задаци који припадају пословној књизи додељеној *<предузећу>*", + "globalSearch-instructions-operator-team": "`__operator_team__:<назив тима|скраћени назив>` - задаци који припадају пословној књизи додељеној *<тиму>*", + "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - задаци чији рок истиче за *<n>* дана. `__operator_due__:__predicate_overdue__ приказује списак свих задатака са истеклим роком.", + "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - задаци који су постављени пре *<n>* или мање дана", + "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - задаци који су измењени пре *<n>* или мање дана", "globalSearch-instructions-operator-status": "`__operator_status__:<стање>` - где се под *<стањем>* подразумева једно од следећих:", - "globalSearch-instructions-status-archived": "`__predicate_archived__` - архивирани предмети", + "globalSearch-instructions-status-archived": "`__predicate_archived__` - архивирани задаци", "globalSearch-instructions-status-all": "`__predicate_all__` - сви архивирани и неархивирани задаци", - "globalSearch-instructions-status-ended": "`__predicate_ended__` - сви окончани предмети", - "globalSearch-instructions-status-public": "`__predicate_public__` - предмети видљиви свима на интернету", - "globalSearch-instructions-status-private": "`__predicate_private__` - предмети видљиви искључиво сарадницима", - "globalSearch-instructions-operator-has": "`__operator_has__:<поље>` - где је то *<поље>* једно од `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` или `__predicate_member__`. Уколико уметнете знак минус `-` испред тог *<поља>* претражује се недостатак те вредности (нпр `има:-рок` претражује предмете без постављеног рока).", + "globalSearch-instructions-status-ended": "`__predicate_ended__` - задаци где је постављен датум завршетка", + "globalSearch-instructions-status-public": "`__predicate_public__` - једино задаци у јавним пословним књигама", + "globalSearch-instructions-status-private": "`__predicate_private__` - једино задаци у приватним пословним књигама", + "globalSearch-instructions-operator-has": "`__operator_has__:<поље>` - где је то *<поље>* једно од `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` или `__predicate_member__`. Уколико уметнете знак минус `-` испред тог *<поља>* претражује се недостатак те вредности (нпр `има:-рок` претражује задатке без постављеног рока).", "globalSearch-instructions-operator-sort": "`__operator_sort__:<правило>` - где је *<правило>* једно од `__predicate_due__`, `__predicate_created__` или `__predicate_modified__`. За опадајући редослед уметните минус `-` испред правила.", - "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - позитиван цео број *<n>* представља број предмета који се приказују по страни.", + "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - позитиван цео број *<n>* представља број задатака који се приказују по страни.", "globalSearch-instructions-notes-1": "Можете истовремено задати више оператора.", - "globalSearch-instructions-notes-2": "Над сродним операторима се изводи операција логичко ИЛИ. Биће приказани предмети који испуњавају било који од услова.\n`__operator_list__:Припрема __operator_list__:Жалба` као резултат приказује све предмете које су било у деоници *Припрема* било у деоници *Жалба*.", - "globalSearch-instructions-notes-3": "На несродним операторима се изводи операција логичко И. Биће приказани само они задаци који испуњавају све услове. `__operator_list__:Жалба __operator_label__:црвена` приказаће међу свим предметима у *жалбеном* делу поступка само оне са *црвеном* налепницом.", + "globalSearch-instructions-notes-2": "Над поновљеним операторима се изводи операција логичко ИЛИ. Биће приказани задаци који испуњавају било који од услова.\n`__operator_list__:Доступно __operator_list__:Блокирано` као резултат приказује све картице које су било у деоници *Блокирано* било у деоници *Доступно*.", + "globalSearch-instructions-notes-3": "На различитим операторима се изводи операција логичко И. Биће приказани само они задаци који испуњавају све услове. `__operator_list__:Доступно __operator_label__:црвено` приказаће међу свим задацима на деоници *Доступно* само оне са *црвеном* налепницом.", "globalSearch-instructions-notes-3-2": "Дани се могу задати као позитивни или негативни цели бројеви или се користе `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` за текући период.", - "globalSearch-instructions-notes-4": "Разлика између малих и великих слова се не узима у обзир.", - "globalSearch-instructions-notes-5": "Ако нисте ништа дирали, претрагом неће бити обухваћени предмети из архиве.", + "globalSearch-instructions-notes-4": "Претрага речи не прави разлику између малих и великих слова.", + "globalSearch-instructions-notes-5": "Преподешено је да се архивиране картице са задацима не претражују.", "link-to-search": "Повежи до ове претраге", "excel-font": "Arial словни лик", "number": "Број", - "label-colors": "Налепнице у боји", + "label-colors": "Боје налепница", "label-names": "Натписи на налепницама", - "archived-at": "датум и време архивирања", - "sort-cards": "Сложи предмете", - "sort-is-on": "Предмети су сложени", - "cardsSortPopup-title": "Сложи предмете", - "due-date": "По крајњем року", + "archived-at": "време архивирања", + "sort-cards": "Пресложи картице", + "sort-is-on": "Слагање је укључено", + "cardsSortPopup-title": "Пресложи картице", + "due-date": "Датум истека", "server-error": "Грешка на серверу", "server-error-troubleshooting": "Молим да нам пошаљете извештај о грешци коју је изазвао сервер.\nАко је у питању snap инсталација, покрените: `sudo snap logs wekan.wekan`\nАко је у питању Docker инсталација, покрените: `sudo docker logs wekan-app`", - "title-alphabetically": "По наслову абучним редом", - "created-at-newest-first": "По најновијим запримљеним", - "created-at-oldest-first": "По најстарије запримљеним", + "title-alphabetically": "Наслов (Азбучним редом)", + "created-at-newest-first": "Кад је направљено (Најновије прво)", + "created-at-oldest-first": "Кад је направљено (Најстарије прво)", "links-heading": "Везе", "hide-activities-of-all-boards": "Don't show the board activities on all boards", "now-activities-of-all-boards-are-hidden": "Now all activities of all boards are hidden", - "move-swimlane": "Премести ток поступка", - "moveSwimlanePopup-title": "Премештање тока поступка", - "custom-field-stringtemplate": "Образац за словни низ", + "move-swimlane": "Премести стазу", + "moveSwimlanePopup-title": "Премештање стазе", + "custom-field-stringtemplate": "Предложак за словни низ", "custom-field-stringtemplate-format": "Облик (користите %{вредност} као основу/носач)", "custom-field-stringtemplate-separator": "Раздвајач (користите или   за размак)", "custom-field-stringtemplate-item-placeholder": "Притисните ентер да додате још ставки", - "creator": "Оснивач", - "creator-on-minicard": "Завео", - "filesReportTitle": "Извештај везан за предметну грађу", + "creator": "Задао", + "creator-on-minicard": "Creator on minicard", + "filesReportTitle": "Извештај везан за датотеке", "reports": "Извештаји", "rulesReportTitle": "Извештај везан за правила", - "boardsReportTitle": "Извештај везан за списе", - "cardsReportTitle": "Извештај везан за предмете", - "copy-swimlane": "Умножи ток поступка", - "copySwimlanePopup-title": "Умножавање тока поступка", - "display-card-creator": "Прикажи ко је завео предмет", - "wait-spinner": "Док се исчитавају списи", + "boardsReportTitle": "Извештај везан за пословне књиге", + "cardsReportTitle": "Извештај везан за картице", + "copy-swimlane": "Умножи стазу", + "copySwimlanePopup-title": "Умножавање стазе", + "display-card-creator": "Прикажи ко је направио картицу", + "wait-spinner": "Док се исчитава пословна књига", "Bounce": "Исцртавај три тачкице", "Cube": "Исцртавај квадратиће", "Cube-Grid": "Исцртавај мрежу квадратића", @@ -1222,16 +1171,16 @@ "Rotateplane": "Исцртавај лист који се окреће", "Scaleout": "Исцртавај удаљавање", "Wave": "Исцртавај таласе", - "maximize-card": "Рашири предметне списе", - "minimize-card": "Састави предметне списе", - "delete-org-warning-message": "Не могу да избацим ову странку пошто постоји барем један везани сарадник", - "delete-team-warning-message": "Не могу да распустим овај правни тим пошто постоји барем једно лице које му припада", + "maximize-card": "Увећај картицу", + "minimize-card": "Умањи картицу", + "delete-org-warning-message": "Не могу да угасим ово предузеће пошто постоји барем један корисник који му припада", + "delete-team-warning-message": "Не могу да расформирам овај тим пошто постоји барем један корисник који му припада", "subject": "Тема", "details": "Детаљи", "carbon-copy": "још један примерак", - "ticket": "Пријава квара", - "tickets": "Пријављени кварови", - "ticket-number": "Пријавни број", + "ticket": "Предмет", + "tickets": "Предмети", + "ticket-number": "Број предмета", "open": "Отворен", "pending": "На чекању", "closed": "Затворен", @@ -1241,14 +1190,14 @@ "request": "Захтев", "requests": "Захтеви", "help-request": "Захтева се помоћ", - "editCardSortOrderPopup-title": "Промените редослед слагања предмета", - "cardDetailsPopup-title": "Шире радње на предмету", + "editCardSortOrderPopup-title": "Промените правило слагања картица", + "cardDetailsPopup-title": "Појединости картице", "add-teams": "Додај тимове", "add-teams-label": "Додати тимови су приказани доле:", - "remove-team-from-table": "Да ли сте сигурни да желите да овом правном тиму укинете увид у ове списе ?", + "remove-team-from-table": "Да ли сте сигурни да желите да уклоните овај тим из пословне књиге ?", "confirm-btn": "Потврди", "remove-btn": "Уклони", - "filter-card-title-label": "Издвој по наслову предмета", + "filter-card-title-label": "Издвој по наслову картице са задатком", "invite-people-success": "Позив за сарадњу је успешно послат", "invite-people-error": "Догодила се грешка приликом слања позива за сарадњу", "can-invite-if-same-mailDomainName": "Домен за електронску пошту", @@ -1268,419 +1217,287 @@ "Node_memory_usage_heap_total": "Node искоришћење меморије: укупан простор који је алоциран за heap", "Node_memory_usage_heap_used": "Node искоришћење меморије: колико заправо је искоришћено", "Node_memory_usage_external": "Node искоришћење меморије: спољна", - "add-organizations": "Додај странке", - "add-organizations-label": "Странке које имају увид су приказана доле:", - "remove-organization-from-board": "Да ли сте сигурни да желите да овој странци забраните увид у ове списе ?", - "to-create-organizations-contact-admin": "Да би могли да додајете странке, молим да се обратите управнику/администратору.", + "add-organizations": "Додај предузећа", + "add-organizations-label": "Додана предузећа су приказана доле:", + "remove-organization-from-board": "Да ли сте сигурни да желите да уклоните ово предузеће из ове пословне књиге ?", + "to-create-organizations-contact-admin": "Да би могли да додајете предузећа, молим да се обратите управнику/администратору.", "custom-legal-notice-link-url": "Место на Интернету са условима/уговором за коришћење", "acceptance_of_our_legalNotice": "Ако наставите даље, сагласни сте да прихватате наше", "legalNotice": "услови коришћења", "copied": "Умножено!", - "checklistActionsPopup-title": "Руковање списковима радњи", - "moveChecklist": "Премести списак радњи", - "moveChecklistPopup-title": "Премести списак радњи", - "newlineBecomesNewChecklistItem": "Сваки ред текста постаје ставка на списку", - "newLineNewItem": "Један ред = једна ставка", - "newlineBecomesNewChecklistItemOriginOrder": "Сваки ред текста постаје ставка на списку - по изворном редоследу уноса", - "originOrder": "изворни редослед", + "checklistActionsPopup-title": "Радње на списковима за обавити", + "moveChecklist": "Премести списак за обавити", + "moveChecklistPopup-title": "Премести списак за обавити", + "newlineBecomesNewChecklistItem": "Each line of text becomes one of the checklist items", + "newLineNewItem": "One line of text = one checklist item", + "newlineBecomesNewChecklistItemOriginOrder": "Each line of text becomes one of the checklist items, original order", + "originOrder": "original order", "copyChecklist": "Умножи списак", "copyChecklistPopup-title": "Умножи списак", - "card-show-lists": "Избор дела поступка", - "subtaskActionsPopup-title": "Радње на издвојеним пословима", - "attachmentActionsPopup-title": "Поступање са овим материјалом", - "attachment-move-storage-fs": "Пренеси материјал у локални систем датотека", - "attachment-move-storage-gridfs": "Пренеси материјал у GridFS", - "attachment-move-storage-s3": "Пренеси материјал у облак на Amazon S3", - "attachment-move": "Пренеси материјал", - "move-all-attachments-to-fs": "Премести целокупну предметну грађу у локални систем датотека", - "move-all-attachments-to-gridfs": "Премести целокупну предметну грађу у MongoDB GridFS", - "move-all-attachments-to-s3": "Премести целокупну предметну грађу из списа у облак на Amazon S3", - "move-all-attachments-of-board-to-fs": "Премести предметну грађу ових списа у локални систем датотека", - "move-all-attachments-of-board-to-gridfs": "Премести предметну грађу ових списа у MongoDB GridFS", - "move-all-attachments-of-board-to-s3": "Премести предметну грађу ових списа у облак на Amazon S3", + "card-show-lists": "Прикажи спискове на картици", + "subtaskActionsPopup-title": "Радње на под задацима", + "attachmentActionsPopup-title": "Однос према прилозима", + "attachment-move-storage-fs": "Премести прилог у локални систем датотека", + "attachment-move-storage-gridfs": "Премести прилог у GridFS", + "attachment-move-storage-s3": "Премести прилог у облак на S3", + "attachment-move": "Премести прилог", + "move-all-attachments-to-fs": "Премести све прилоге у локални систем датотека", + "move-all-attachments-to-gridfs": "Премести све прилоге у GridFS", + "move-all-attachments-to-s3": "Премести све прилоге у облак на S3", + "move-all-attachments-of-board-to-fs": "Премести све прилоге пословне књиге у локални систем датотека", + "move-all-attachments-of-board-to-gridfs": "Премести све прилоге пословне књиге у GridFS", + "move-all-attachments-of-board-to-s3": "Премести све прилоге пословне књиге у облак на S3", "path": "Путања", "version-name": "Назив издања", "size": "Величина", "storage": "Складиште", "action": "Радња", - "board-title": "Деловодни број", + "board-title": "Натпис на пословној књизи", "attachmentRenamePopup-title": "Преименуј", "uploading": "Подижем на сервер", "remaining_time": "Преостало време", "speed": "Брзина", "progress": "Напредак", - "password-again": "Поновљена лозинка", - "if-you-already-have-an-account": "Ако већ поседујете налог ⇨ ", - "register": "Упиши се", + "password-again": "Лозинка (понови је)", + "if-you-already-have-an-account": "Ако већ имате један налог", + "register": "Регистрација", "forgot-password": "Заборављена лозинка", - "minicardDetailsActionsPopup-title": "Предмет у омоту", + "minicardDetailsActionsPopup-title": "Појединости картице", "Mongo_sessions_count": "Број Mongo сесија", - "change-visibility": "Промени тајност списа", + "change-visibility": "Промени видљивост", "max-upload-filesize": "Максимална величина датотеке за слање (у бајтима):", "allowed-upload-filetypes": "Дозвољене врсте датотека за слање:", - "max-avatar-filesize": "Максимална величина слике (у бајтима):", + "max-avatar-filesize": "Максимална величина сличице (у бајтима):", "allowed-avatar-filetypes": "Дозвољене врсте слика:", - "invalid-file": "Ако са именом нешто није у реду тада ће и слање и преименовање бити отказано.", - "preview-pdf-not-supported": "Ваш уређај не подржава приказивање pdf докумената. Пробајте да преузмете документ.", - "drag-board": "Пребаци списе", - "translation-number": "Број исправки превода је:", - "delete-translation-confirm-popup": "Да ли сте сигурни да желите да избришете ову исправку превода? Опозив ове радње неће бити могућ.", - "newTranslationPopup-title": "Нова исправка превода", - "editTranslationPopup-title": "Исправи превод", - "settingsTranslationPopup-title": "Обриши ову исправку превода", - "translation": "Речник", - "text": "Изворна реченица", - "translation-text": "Превод", - "show-subtasks-field": "Прикажи поље издвојени послови", + "invalid-file": "Ако са именом датотеке нешто није у реду тада ће и слање и преименовање бити отказано.", + "preview-pdf-not-supported": "Ваш уређај не подржава приказивање ПДФ докумената. Пробајте да преузмете документ.", + "drag-board": "Пренеси пословну књигу", + "translation-number": "Број прилагођених превода је:", + "delete-translation-confirm-popup": "Да ли сте сигурни да желите да избришете овај прилагођени превод? Опозив ове радње неће бити могућ.", + "newTranslationPopup-title": "Нови прилагођени превод", + "editTranslationPopup-title": "Уреди прилагођени превод", + "settingsTranslationPopup-title": "Обриши овај прилагођени превод", + "translation": "Превод", + "text": "Текст", + "translation-text": "Превод текста", + "show-subtasks-field": "Прикажи поље за подзадатке", "show-week-of-year": "Show week of year (ISO 8601)", - "convert-to-markdown": "Претвори у структуирани текст", - "import-board-zip": "Додај .zip архиву која има списе у json облику и фасцикле по имену списа где је предметна грађа", - "collapse": "Скупи", - "uncollapse": "Рашири", - "hideCheckedChecklistItems": "Сакриј све обављене помоћне предметне радње", - "hideAllChecklistItems": "Сакриј све помоћне предметне радње", - "support": "Подршка", - "supportPopup-title": "Подршка", - "support-page-enabled": "Неопходна ми је техничка подршка", - "support-info-not-added-yet": "Информације о техничкој подршци још увек нису додате", - "support-info-only-for-logged-in-users": "Информације о техничкој подршци се дају само пријављеним корисницима.", - "support-title": "Наслов", - "support-content": "Садржај", - "accessibility": "Особе са посебним потешкоћама", - "accessibility-page-enabled": "Омогући страницу за особе са посебним потешкоћама", - "accessibility-info-not-added-yet": "Информације намењене особама са посебним потешкоћама, за сада, нису додате", - "accessibility-title": "Наслов такве странице", - "accessibility-content": "Садржај такве странице", - "accounts-lockout-settings": "Заштитне мере од насилног упада", - "accounts-lockout-info": "Овим мерама се штитимо од насилних покушаја пријављивања погађањем лозинкеf.", - "accounts-lockout-known-users": "Мере за познате налоге (где налог постоји али се лозинка не подудара)", - "accounts-lockout-unknown-users": "Мере за непознате налоге (где сарадник са таквим корисничким именом не постоји)", - "accounts-lockout-failures-before": "Граница неуспешних покушаја пријаве (након које се сараднику одузима право приступа)", - "accounts-lockout-period": "Трајање мере забране (у секундама)", - "accounts-lockout-failure-window": "Временски оквир у којем се одиграва насилан упад (у секундама)", - "accounts-lockout-settings-updated": "Мере против насилног упада су допуњене", - "accounts-lockout-locked-users": "Налози на мерама", - "accounts-lockout-locked-users-info": "Сарадници којима је одузето право приступа због превеликог броја неуспешних покушаја пријаве", - "accounts-lockout-no-locked-users": "Тренутно не постоје налози са мером забране права приступа", - "accounts-lockout-failed-attempts": "Број неуспешних покушаја", - "accounts-lockout-remaining-time": "Преостало време", - "accounts-lockout-user-unlocked": "Налог је успешно скинут са мера забране приступа", - "accounts-lockout-confirm-unlock": "Да ли сте сигурни да желите да скинете овај налог са мера?", - "accounts-lockout-confirm-unlock-all": "Да ли сте сигурни да желите да скинете све налоге са мера?", - "accounts-lockout-show-locked-users": "Прикажи само налоге на мерама", - "accounts-lockout-user-locked": "Налог је на мерама", - "accounts-lockout-click-to-unlock": "Притисните да налог добије право приступа", - "accounts-lockout-status": "Право приступа", - "admin-people-filter-show": "Издвој:", - "admin-people-filter-all": "Обухвати све расположиве сараднике", - "admin-people-filter-locked": "Налоге на мерама забране права приступа", - "admin-people-filter-active": "Сарадник је на платном списку", - "admin-people-filter-inactive": "Сарадник више није на платном списку", - "admin-people-active-status": "Радни однос", - "admin-people-user-active": "Сарадник је на платном списку - притисните да би прекинули сарадњу", - "admin-people-user-inactive": "Сарадник није на платном списку - притисните да би га увели на платни списак", - "accounts-lockout-all-users-unlocked": "Сви сарадници којима је претходно било одузето право пријаве су скинути са ове мере", - "accounts-lockout-unlock-all": "Скини све са мера", - "active-cron-jobs": "Већ унапред заказани послови:", - "add-cron-job": "Закажи нови посао", - "add-cron-job-placeholder": "Ова функционалност биће ускоро додата", - "attachment-storage-configuration": "Поставка складишта предметне грађе", - "attachments-path": "Пуна путања до складишта", - "attachments-path-description": "Путања до складишта где се чува предметна грађа", - "avatars-path": "Путања до слика сарадника", - "avatars-path-description": "Путања где се складиште слике сарадника", - "board-archive-failed": "Није успело заказивање архивирања списа", - "board-archive-scheduled": "Заказано је паковање списа у архиву", - "board-backup-failed": "Није успело заказивање израде резервног примерка списа", - "board-backup-scheduled": "Заказана је израда резервног примерка списа", - "board-cleanup-failed": "Није успело заказивање чишћења списа", - "board-cleanup-scheduled": "Заказано је чишћење списа", - "board-operations": "Радње на списима", - "cron-jobs": "Заказани послови", - "cron-migrations": "Заказани поступци обнове оштећених списа", - "cron-job-delete-confirm": "Да ли сте сигурни да желите да уклоните заказано?", - "cron-job-delete-failed": "Није успело уклањање заказаног посла", - "cron-job-deleted": "Успешно је уклоњен заказани посао", - "cron-job-pause-failed": "Није успело паузирање заказаног посла", - "cron-job-paused": "Заказани посао је успешно паузиран", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Грешке приликом обнове списа", - "cron-migration-warnings": "Упозорења приликом обнове списа", - "cron-no-errors": "Нису се догодиле никакве грешке", - "cron-error-severity": "Учесталост", - "cron-error-time": "Време", - "cron-error-message": "Порука о грешци", - "cron-error-details": "Појединости", - "cron-clear-errors": "Избриши све наведене грешке", - "cron-retry-failed": "Понови неуспешне обнове", - "cron-resume-paused": "Настави обнову након предаха", - "cron-errors-cleared": "Све грешке су успешно очишћене", - "cron-no-failed-migrations": "Нема понављања јер није ни било неуспешних обнова", - "cron-no-paused-migrations": "Нема настављања јер се није ни правио предах", - "cron-migrations-resumed": "Обнове су успешно настављене", - "cron-migrations-retried": "Неуспеле обнове су управо поново покренуте", - "complete": "Посао је успешно обављен", - "idle": "Стање мировања", - "filesystem-path-description": "Почетак пута до складишта предметне грађе", - "gridfs-enabled": "GridFS ради и можете га користити", - "gridfs-enabled-description": "Можете користити MongoDB GridFS за складиште предметне грађе", - "all-migrations": "Све обнове", - "select-migration": "Изабрана обнова", - "start": "Започет", - "pause": "Предах", - "stop": "Заустави", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", - "migration-pause-failed": "Није било могуће направити предах у поступку обнове оштећених списа", - "migration-paused": "Направљен је предах у поступку обнове оштећених списа", - "migration-progress": "Напредак у току обнове", - "migration-start-failed": "Није било могуће покренути обнову оштећених списа", - "migration-started": "Обнова оштећених списа је започета", - "migration-not-needed": "No migration needed", - "migration-status": "Пресек стања", - "migration-stop-confirm": "Да ли сте сигурни да желите да зауставите све поступке обнове оштећених предметних списа?", - "migration-stop-failed": "Није било могуће прекинути поступке обнове оштећених списа", - "migration-stopped": "Поступци обнове оштећених списа су управо заустављени", - "mongodb-gridfs-storage": "MongoDB GridFS складиште", - "pause-all-migrations": "Предах за све", - "s3-access-key": "S3 приступни кључ", - "s3-access-key-description": "AWS S3 приступни кључ за пријаву", - "s3-access-key-placeholder": "Унесите S3 приступни кључ", + "convert-to-markdown": "Претвори у маркдаун", + "import-board-zip": "Add .zip file that has board JSON files, and board name subdirectories with attachments", + "collapse": "Сажми", + "uncollapse": "Uncollapse", + "hideCheckedChecklistItems": "Hide checked checklist items", + "hideAllChecklistItems": "Hide all checklist items", + "support": "Support", + "supportPopup-title": "Support", + "accessibility": "Accessibility", + "accessibility-page-enabled": "Accessibility page enabled", + "accessibility-info-not-added-yet": "Accessibility info has not been added yet", + "accessibility-title": "Accessibility title", + "accessibility-content": "Accessibility content", + "accounts-lockout-settings": "Brute Force Protection Settings", + "accounts-lockout-info": "These settings control how login attempts are protected against brute force attacks.", + "accounts-lockout-known-users": "Settings for known users (correct username, wrong password)", + "accounts-lockout-unknown-users": "Settings for unknown users (non-existent username)", + "accounts-lockout-failures-before": "Failures before lockout", + "accounts-lockout-period": "Lockout period (seconds)", + "accounts-lockout-failure-window": "Failure window (seconds)", + "accounts-lockout-settings-updated": "Brute force protection settings have been updated", + "accounts-lockout-locked-users": "Locked Users", + "accounts-lockout-locked-users-info": "Users currently locked out due to too many failed login attempts", + "accounts-lockout-no-locked-users": "There are currently no locked users", + "accounts-lockout-failed-attempts": "Failed Attempts", + "accounts-lockout-remaining-time": "Remaining Time", + "accounts-lockout-user-unlocked": "User has been unlocked successfully", + "accounts-lockout-confirm-unlock": "Are you sure you want to unlock this user?", + "accounts-lockout-confirm-unlock-all": "Are you sure you want to unlock all locked users?", + "accounts-lockout-show-locked-users": "Show locked users only", + "accounts-lockout-user-locked": "User is locked", + "accounts-lockout-click-to-unlock": "Click to unlock this user", + "accounts-lockout-status": "Стање", + "admin-people-filter-show": "Show:", + "admin-people-filter-all": "Недовршени послови сарадника", + "admin-people-filter-locked": "Locked Users Only", + "admin-people-filter-active": "На окупу", + "admin-people-filter-inactive": "Not Active", + "admin-people-active-status": "Active Status", + "admin-people-user-active": "User is active - click to deactivate", + "admin-people-user-inactive": "User is inactive - click to activate", + "accounts-lockout-all-users-unlocked": "All locked users have been unlocked", + "accounts-lockout-unlock-all": "Unlock All", + "active-cron-jobs": "Active Scheduled Jobs", + "add-cron-job": "Add Scheduled Job", + "add-cron-job-placeholder": "Add Scheduled Job functionality coming soon", + "attachment-storage-configuration": "Attachment Storage Configuration", + "attachments-path": "Attachments Path", + "attachments-path-description": "Path where attachment files are stored", + "avatars-path": "Avatars Path", + "avatars-path-description": "Path where avatar files are stored", + "board-archive-failed": "Failed to schedule board archive", + "board-archive-scheduled": "Board archive scheduled successfully", + "board-backup-failed": "Failed to schedule board backup", + "board-backup-scheduled": "Board backup scheduled successfully", + "board-cleanup-failed": "Failed to schedule board cleanup", + "board-cleanup-scheduled": "Board cleanup scheduled successfully", + "board-operations": "Board Operations", + "cron-jobs": "Scheduled Jobs", + "cron-migrations": "Scheduled Migrations", + "cron-job-delete-confirm": "Are you sure you want to delete this scheduled job?", + "cron-job-delete-failed": "Failed to delete scheduled job", + "cron-job-deleted": "Scheduled job deleted successfully", + "cron-job-pause-failed": "Failed to pause scheduled job", + "cron-job-paused": "Scheduled job paused successfully", + "filesystem-path-description": "Base path for file storage", + "gridfs-enabled": "GridFS Enabled", + "gridfs-enabled-description": "Use MongoDB GridFS for file storage", + "migration-pause-failed": "Failed to pause migrations", + "migration-paused": "Migrations paused successfully", + "migration-progress": "Migration Progress", + "migration-start-failed": "Failed to start migrations", + "migration-started": "Migrations started successfully", + "migration-status": "Migration Status", + "migration-stop-confirm": "Are you sure you want to stop all migrations?", + "migration-stop-failed": "Failed to stop migrations", + "migration-stopped": "Migrations stopped successfully", + "mongodb-gridfs-storage": "MongoDB GridFS Storage", + "pause-all-migrations": "Pause All Migrations", + "s3-access-key": "S3 Access Key", + "s3-access-key-description": "AWS S3 access key for authentication", + "s3-access-key-placeholder": "Enter S3 access key", "s3-bucket": "S3 Bucket", - "s3-bucket-description": "S3 bucket име где чувате предметну грађу", - "s3-connection-failed": "S3 веза је у прекиду", - "s3-connection-success": "Управо је остварена веза до S3", - "s3-enabled": "S3 ради и може да се користи", - "s3-enabled-description": "Можете да користите AWS S3 или MinIO као складиште предметне грађе", - "s3-endpoint": "S3 Endpoint тачка", - "s3-endpoint-description": "S3 endpoint тачка (нпр. s3.amazonaws.com или minio.example.com)", - "s3-minio-storage": "S3/MinIO складиште", + "s3-bucket-description": "S3 bucket name for storing files", + "s3-connection-failed": "S3 connection failed", + "s3-connection-success": "S3 connection successful", + "s3-enabled": "S3 Enabled", + "s3-enabled-description": "Use AWS S3 or MinIO for file storage", + "s3-endpoint": "S3 Endpoint", + "s3-endpoint-description": "S3 endpoint URL (e.g., s3.amazonaws.com or minio.example.com)", + "s3-minio-storage": "S3/MinIO Storage", "s3-port": "S3 Port", - "s3-port-description": "S3 endpoint port број", + "s3-port-description": "S3 endpoint port number", "s3-region": "S3 Region", - "s3-region-description": "AWS S3 регион (нпр. us-east-1)", - "s3-secret-key": "S3 тајни кључ", - "s3-secret-key-description": "AWS S3 тајни кључ за пријаву", - "s3-secret-key-placeholder": "Унесите S3 тајни кључ", - "s3-secret-key-required": "Тражи се и S3 тајни кључ", - "s3-settings-save-failed": "Није могла бити сачувана S3 поставка", - "s3-settings-saved": "S3 поставка је сачувана", - "s3-ssl-enabled": "S3 SSL шифровање је укључено", - "s3-ssl-enabled-description": "Користи SSL/TLS за везу до Amazon S3 складишта", - "save-s3-settings": "Сачувај ову S3 поставку", - "schedule-board-archive": "Закажи архивирање списа", - "schedule-board-backup": "Закажи израду резервног примерка", - "schedule-board-cleanup": "Закажи чишћење", - "scheduled-board-operations": "Заказане радње на списима", - "start-all-migrations": "Пуна обнова", - "stop-all-migrations": "Прекини поступке", - "test-s3-connection": "Проба везе до Amazon S3", - "writable-path": "Проходна путања", - "writable-path-description": "Почетак путање према складишту предметне грађе", - "add-job": "Додај посао", - "attachment-migration": "Пресељење предметне грађе", - "attachment-monitoring": "Надзор над предметном грађом", - "attachment-settings": "Рад са предметном грађом", - "attachment-storage-settings": "Складиште предметне грађе", - "automatic-migration": "Обнављај иза у тишини", - "back-to-settings": "Натраг на поставку", - "board-id": "BoardID", - "board-migration": "Обнова оштећених списа", - "board-migrations": "Поступци обнове оштећених списа", - "card-show-lists-on-minicard": "Припадајући део поступка", - "comprehensive-board-migration": "Свеобухватна обнова", - "comprehensive-board-migration-description": "Изводе се свеобухватне провера и врше поправке целовитости података у списима - што укључује провере и поправке редоследа делова поступака, места где се тачно предмети налазе и структуре поступака.", - "delete-duplicate-empty-lists-migration": "Брисање истоимених делова поступка", - "delete-duplicate-empty-lists-migration-description": "На један безбедан начин брише празне истоимене делове поступка. Бришу се сви делови поступка у којима нема предмета ако постоји део поступка са истим тим насловом где већ постоје предмети.", - "lost-cards": "Загубљени предмети", - "lost-cards-list": "Опорављени предмети", - "restore-lost-cards-migration": "Опоравак загубљених предмета", - "restore-lost-cards-migration-description": "Открива и опоравља предмете и делове поступака којима недостају поља swimlaneId или listId и смешта у „Опорављене предмете“.", - "restore-all-archived-migration": "Опоравак свих предмета из архиве", - "restore-all-archived-migration-description": "Опоравља све архивиране делове поступака, поступке и предмете притом поправљајући недостајућа поља swimlaneId или listId.", - "fix-missing-lists-migration": "Поправка кад недостају делови поступка", - "fix-missing-lists-migration-description": "Открива и поправља сваки део поступка који недостаје или који је оштећен.", - "fix-avatar-urls-migration": "Поправка кад се не виде слике сарадника", - "fix-avatar-urls-migration-description": "Исправља везу до складишта са сликама сарадника из ових списа.", - "fix-all-file-urls-migration": "Поправка кад ишчезне предметна грађа", - "fix-all-file-urls-migration-description": "Преусмерава везу ка складишту где се стварно налази предметна грађа из ових списа.", - "migration-needed": "Потребна је обнова", - "migration-complete": "Обнова је обављена", - "migration-running": "Обнова је у току...", - "migration-successful": "Обнова је успешно окончана", - "migration-failed": "Обнова није била успешна", - "migrations": "Радионица", - "migrations-admin-only": "За обнову ових списа надлежан је једино онај ко је њихов управник", - "migrations-description": "Овде може да се изврши провера целовитости података и поправка оштећења. Сваки поступак обнове се може спровести независно један од другог", - "no-issues-found": "Нису уочена оштећења", - "run-migration": "Покрени опоравак", - "run-comprehensive-migration-confirm": "Овим ће бити изведене свеобухватне провере и поправке целовитости података у списима. То би кратко трајало уколико сте сагласни?", - "run-delete-duplicate-empty-lists-migration-confirm": "Овим ће прво сви дељени делови поступака бити везани на саме поступке, затим ће бити избрисан сваки део поступка који је празан где постоји део поступка истог имена који има предмете. Да ли сте сагласни?", - "run-restore-lost-cards-migration-confirm": "Овим се прави ток „Загубљени предмети“ и у њега смештају сви предмети и делови поступка којима недостају поља swimlaneId или listId. Мисли се на оно што је загубљено али није загубљено у архиви. Да ли сте сагласни? ", - "run-restore-all-archived-migration-confirm": "Овим се износи натраг из архиве све оно што сте до сада спаковали тамо. Поља где недостаје id биће поправљена. Упозорење! Да ли заиста то желите?", - "run-fix-missing-lists-migration-confirm": "Овим се откривају и поправљају они делови поступака који недостају или који су оштећени. Да ли сте сагласни?", - "run-fix-avatar-urls-migration-confirm": "Овим се исправља веза до складишта са сликама сарадника из ових списа. Да ли сте сагласни?", - "run-fix-all-file-urls-migration-confirm": "Овим ће бити исправљене све везе у списима да упућују на стварно складиште предметне грађе. Да ли сте сагласни?", - "restore-lost-cards-nothing-to-restore": "Нема нити загубљених поступака нити предмета за опоравак", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Укупни напредак", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Стање", - "migration-progress-details": "Појединости", - "migration-progress-note": "Молимо да будете стрпљиви док траје препакивање Ваших списа...", - "steps": "кораци", - "view": "Поглед", - "has-swimlanes": "има више поступака", - - "step-analyze-board-structure": "Изучавам везе у списима", - "step-fix-orphaned-cards": "Поправљам одбачене предмете", - "step-convert-shared-lists": "Претварам дељене делове поступка", - "step-ensure-per-swimlane-lists": "Осигуравам да је део поступка везан за поступак", - "step-validate-migration": "Оцењујем обнову", - "step-fix-avatar-urls": "Поправљам везе до складишта слика за сараднике", - "step-fix-attachment-urls": "Поправљам везе до складишта предметне грађе", - "step-analyze-lists": "Изучавам делове поступка", - "step-create-missing-lists": "Стварам недостајуће делове", - "step-update-cards": "Допуњујем предмете", - "step-finalize": "Завршавам", - "step-delete-duplicate-empty-lists": "Бришем исте празне делове поступка", - "step-ensure-lost-cards-swimlane": "Правим место за загубљене предмете", - "step-restore-lists": "Опорављам делове поступка", - "step-restore-cards": "Опорављам предмете", - "step-restore-swimlanes": "Опорављам читав ток поступка", - "step-fix-missing-ids": "Поправљам недостајућа id поља", - "step-scan-users": "Проверавам слике сарадника на списима", - "step-scan-files": "Проверавам предметну грађу", - "step-fix-file-urls": "Поправљам везе до предметне грађе", - "cleanup": "Чистим", - "cleanup-old-jobs": "Чистим остављено", - "completed": "Прошао сам кроз све кораке", - "conversion-info-text": "Ово преслагање се изводи само једном по спису и то после олакшава читање. Без сметњи настављате да читате спис након тога.", - "converting-board": "Преслагање списа", - "converting-board-description": "Преслагање списа олакшава употребу. Ово може потрајати неколико тренутака.", - "cpu-cores": "Процесорска језгра", - "cpu-usage": "Заузеће процесора", - "current-action": "Текућа радња", - "database-migration": "Обнова базе података", - "database-migration-description": "Преслажем структуру базе података да би била боља и бржа. Процес може потрајати неколико минута.", - "database-migrations": "Преслагање базе података", - "days-old": "дана стар", - "duration": "Трајање", - "errors": "Грешке", - "estimated-time-remaining": "Процењено преостало време", - "every-1-day": "Једном дневно", - "every-1-hour": "На сваки сат", - "every-1-minute": "На сваки минут", - "every-10-minutes": "На сваких десет минута", - "every-30-minutes": "На сваких пола сата", - "every-5-minutes": "На пет минута", - "every-6-hours": "На шест сати", - "export-monitoring": "Надзор током изношења", - "filesystem-attachments": "Локална предметна грађа", - "filesystem-size": "Величина локалног складишта", - "filesystem-storage": "Локално складиште", - "force-board-scan": "Читање списа на силу", - "gridfs-attachments": "GridFS раздељена предметна грађа", - "gridfs-size": "GridFS величина", + "s3-region-description": "AWS S3 region (e.g., us-east-1)", + "s3-secret-key": "S3 Secret Key", + "s3-secret-key-description": "AWS S3 secret key for authentication", + "s3-secret-key-placeholder": "Enter S3 secret key", + "s3-secret-key-required": "S3 secret key is required", + "s3-settings-save-failed": "Failed to save S3 settings", + "s3-settings-saved": "S3 settings saved successfully", + "s3-ssl-enabled": "S3 SSL Enabled", + "s3-ssl-enabled-description": "Use SSL/TLS for S3 connections", + "save-s3-settings": "Save S3 Settings", + "schedule-board-archive": "Schedule Board Archive", + "schedule-board-backup": "Schedule Board Backup", + "schedule-board-cleanup": "Schedule Board Cleanup", + "scheduled-board-operations": "Scheduled Board Operations", + "start-all-migrations": "Start All Migrations", + "stop-all-migrations": "Stop All Migrations", + "test-s3-connection": "Test S3 Connection", + "writable-path": "Writable Path", + "writable-path-description": "Base directory path for file storage", + "add-job": "Add Job", + "attachment-migration": "Attachment Migration", + "attachment-monitoring": "Attachment Monitoring", + "attachment-settings": "Attachment Settings", + "attachment-storage-settings": "Storage Settings", + "automatic-migration": "Automatic Migration", + "back-to-settings": "Back to Settings", + "board-id": "Board ID", + "board-migration": "Board Migration", + "card-show-lists-on-minicard": "Show Lists on Minicard", + "cleanup": "Cleanup", + "cleanup-old-jobs": "Cleanup Old Jobs", + "completed": "Обављен", + "conversion-info-text": "This conversion is performed once per board and improves performance. You can continue using the board normally.", + "converting-board": "Converting Board", + "converting-board-description": "Converting board structure for improved functionality. This may take a few moments.", + "cpu-cores": "CPU Cores", + "cpu-usage": "CPU Usage", + "current-action": "Current Action", + "database-migration": "Database Migration", + "database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.", + "database-migrations": "Database Migrations", + "days-old": "Days Old", + "duration": "Duration", + "errors": "Errors", + "estimated-time-remaining": "Estimated time remaining", + "every-1-day": "Every 1 day", + "every-1-hour": "Every 1 hour", + "every-1-minute": "Every 1 minute", + "every-10-minutes": "Every 10 minutes", + "every-30-minutes": "Every 30 minutes", + "every-5-minutes": "Every 5 minutes", + "every-6-hours": "Every 6 hours", + "export-monitoring": "Export Monitoring", + "filesystem-attachments": "Filesystem Attachments", + "filesystem-size": "Filesystem Size", + "filesystem-storage": "Filesystem Storage", + "force-board-scan": "Force Board Scan", + "gridfs-attachments": "GridFS Attachments", + "gridfs-size": "GridFS Size", "gridfs-storage": "GridFS", - "hide-list-on-minicard": "Сакриј део поступка са омота", - "idle-migration": "Тиха обнова", - "job-description": "Опис посла", - "job-details": "Појединости посла", - "job-name": "Име посла", - "job-queue": "Посао по реду", - "last-run": "Последње покретање", - "max-concurrent": "Паралелно", - "memory-usage": "Искоришћеност меморије", - "migrate-all-to-filesystem": "Пресели све у локално складиште", - "migrate-all-to-gridfs": "Пресели све и издели у GridFS", - "migrate-all-to-s3": "Пресели све у Amazon S3 облак", - "migrated-attachments": "Пресељена предметна грађа", - "migration-batch-size": "Ширина захвата", - "migration-batch-size-description": "Број јединица предметне грађе који ће бити обрађен при сваком пролазу (1-100)", - "migration-cpu-threshold": "Граница искоришћења процесора (%)", - "migration-cpu-threshold-description": "Направи предах када заузеће процесора досегне ове проценте (10-90)", - "migration-delay-ms": "Задршка (ms)", - "migration-delay-ms-description": "Задршка између пролаза изражена у милисекундама (100-10000)", - "migration-detector": "Детектор", - "migration-info-text": "Преслагање базе података се изводи само једном и то поправља брзину. Ова радња се изводи у позадини чак иако одете.", - "migration-log": "Записник из обнове", - "migration-markers": "Обележја обнове", - "migration-resume-failed": "Није успео наставак обнове", - "migration-resumed": "Обнова је настављена", - "migration-steps": "Кораци у обнови", - "migration-warning-text": "Предлажемо да не затварате прозор током обнове. Чак и тада процес ће бити изведен у позадини али све може дуже да траје.", - "monitoring-export-failed": "Не могу да извезем надзорне податаке", - "monitoring-refresh-failed": "Не могу да освежим надзорне податке", - "next": "Следеће", - "next-run": "Наредни покрет", + "hide-list-on-minicard": "Hide List on Minicard", + "idle-migration": "Idle Migration", + "job-description": "Job Description", + "job-details": "Job Details", + "job-name": "Job Name", + "job-queue": "Job Queue", + "last-run": "Last Run", + "max-concurrent": "Max Concurrent", + "memory-usage": "Memory Usage", + "migrate-all-to-filesystem": "Migrate All to Filesystem", + "migrate-all-to-gridfs": "Migrate All to GridFS", + "migrate-all-to-s3": "Migrate All to S3", + "migrated-attachments": "Migrated Attachments", + "migration-batch-size": "Batch Size", + "migration-batch-size-description": "Number of attachments to process in each batch (1-100)", + "migration-cpu-threshold": "CPU Threshold (%)", + "migration-cpu-threshold-description": "Pause migration when CPU usage exceeds this percentage (10-90)", + "migration-delay-ms": "Delay (ms)", + "migration-delay-ms-description": "Delay between batches in milliseconds (100-10000)", + "migration-detector": "Migration Detector", + "migration-info-text": "Database migrations are performed once and improve system performance. The process continues in the background even if you close your browser.", + "migration-log": "Migration Log", + "migration-markers": "Migration Markers", + "migration-resume-failed": "Failed to resume migration", + "migration-resumed": "Migration resumed", + "migration-steps": "Migration Steps", + "migration-warning-text": "Please do not close your browser during migration. The process will continue in the background but may take longer to complete.", + "monitoring-export-failed": "Failed to export monitoring data", + "monitoring-refresh-failed": "Failed to refresh monitoring data", + "next": "Next", + "next-run": "Next Run", "of": "од", - "operation-type": "Врста радње", - "overall-progress": "Измерени напредак", - "page": "Страна", - "pause-migration": "Направи предах", - "previous": "Претходна", - "refresh": "Освежи", - "refresh-monitoring": "Најновији подаци са надзора", - "remaining-attachments": "Преостала предметна грађа", - "resume-migration": "Настави обнову", - "run-once": "Покрени једном", - "s3-attachments": "Предметна грађа у Amazon S3 облаку", - "s3-size": "Amazon S3 заузеће", + "operation-type": "Operation Type", + "overall-progress": "Overall Progress", + "page": "Page", + "pause-migration": "Pause Migration", + "previous": "Previous", + "refresh": "Refresh", + "refresh-monitoring": "Refresh Monitoring", + "remaining-attachments": "Remaining Attachments", + "resume-migration": "Resume Migration", + "run-once": "Run once", + "s3-attachments": "S3 Attachments", + "s3-size": "S3 Size", "s3-storage": "S3", - "scanning-status": "Изучено стање", - "schedule": "Распоред", - "search-boards-or-operations": "Претрага списа или радњи...", - "show-list-on-minicard": "Прикажи део поступка на омоту", - "showChecklistAtMinicard": "Прикажи предметну радњу на омоту", - "showing": "Приказујем", - "start-test-operation": "Покрени симулацију", - "start-time": "Покрени штоперицу", - "step-progress": "Појединачни напредак", - "stop-migration": "Заустави обнову", - "storage-distribution": "Расподела у складишту", - "system-resources": "Системска снага", - "total-attachments": "Број јединица предметне грађе", - "total-operations": "Укупан број радњи", - "total-size": "Укупна величина", - "unmigrated-boards": "Необновљени списи", - "weight": "Оптерећење", - "cron": "Периодични послови", - "current-step": "Текући корак", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Потврди", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "scanning-status": "Scanning Status", + "schedule": "Schedule", + "search-boards-or-operations": "Search boards or operations...", + "show-list-on-minicard": "Show List on Minicard", + "showing": "Showing", + "start-test-operation": "Start Test Operation", + "start-time": "Start Time", + "step-progress": "Step Progress", + "stop-migration": "Stop Migration", + "storage-distribution": "Storage Distribution", + "system-resources": "System Resources", + "total-attachments": "Total Attachments", + "total-operations": "Total Operations", + "total-size": "Total Size", + "unmigrated-boards": "Unmigrated Boards", + "weight": "Weight", + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/sv.i18n.json b/imports/i18n/data/sv.i18n.json index 6c27167e2..2297e547b 100644 --- a/imports/i18n/data/sv.i18n.json +++ b/imports/i18n/data/sv.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "raderade kommentar %s", "activity-receivedDate": "redigerade mottaget datum till %s av %s", "activity-startDate": "redigerade startdatum till %s av %s", - "allboards.starred": " Stjärnmärkt", - "allboards.templates": "Mallar", - "allboards.remaining": "Återstående", - "allboards.workspaces": "Arbetsytor", - "allboards.add-workspace": "Lägg till arbetsyta", - "allboards.add-workspace-prompt": "Arbetsytans namn", - "allboards.add-subworkspace": "Lägg till underarbetsyta", - "allboards.add-subworkspace-prompt": "Underarbetsytans namn", - "allboards.edit-workspace": "Redigera arbetsyta", - "allboards.edit-workspace-name": "Arbetsytans namn", - "allboards.edit-workspace-icon": "Arbetsytans ikon (markdown)", - "multi-selection-active": "Klicka i kryssrutor för att välja tavlor", "activity-dueDate": "redigerade förfallodag till %s av %s", "activity-endDate": "redigerade slutdatum till %s av %s", "add-attachment": "Lägg till bilaga", @@ -98,7 +86,6 @@ "add-card": "Lägg till kort", "add-card-to-top-of-list": "Lägg till kort överst i listan", "add-card-to-bottom-of-list": "Lägg till kort i botten av listan", - "addListPopup-title": "Lägg till lista", "setListWidthPopup-title": "Ställ in min bredd", "set-list-width": "Ställ in min bredd", "set-list-width-value": "Min och max bredd (pixlar)", @@ -122,10 +109,10 @@ "add-after-list": "Lägg till efter lista", "add-members": "Lägg till medlemmar", "added": "Lades till", - "addMemberPopup-title": "Lägg till medlemmar", + "addMemberPopup-title": "Medlemmar", "memberPopup-title": "Användarinställningar", "admin": "Adminstratör", - "admin-desc": "Kan visa och redigera kort, ta bort medlemmar och ändra inställningar för tavlan. Kan visa aktiviteter.", + "admin-desc": "Kan visa och redigera kort, ta bort medlemmar och ändra inställningarna för tavlan.", "admin-announcement": "Meddelande", "admin-announcement-active": "Aktivt systemövergripande meddelande", "admin-announcement-title": "Meddelande från administratör", @@ -167,16 +154,12 @@ "board-background-image-url": "Bakgrundsbildens URL", "add-background-image": "Lägg till bakgrundsbild", "remove-background-image": "Ta bort bakgrundsbild", - "show-at-all-boards-page": "Visa på sidan alla tavlor", - "board-info-on-my-boards": "Alla tavlors inställningar", - "boardInfoOnMyBoardsPopup-title": "Alla tavlors inställningar", + "show-at-all-boards-page" : "Visa på sidan alla tavlor", + "board-info-on-my-boards" : "Alla tavlors inställningar", + "boardInfoOnMyBoardsPopup-title" : "Alla tavlors inställningar", "boardInfoOnMyBoards-title": "Alla tavlors inställningar", "show-card-counter-per-list": "Visa antal kort per lista", "show-board_members-avatar": "Visa medlemmarnas avatarer", - "board_members": "Alla tavlans medlemmar", - "card_members": "Alla medlemmar i detta kort på tavlan", - "board_assignees": " Alla tilldelade i alla kort på tavlan", - "card_assignees": "Alla tilldelade i detta kort på tavlan", "board-nb-stars": "%s stjärnor", "board-not-found": "Tavla hittades inte", "board-private-info": "Denna tavla kommer vara <strong>privat</strong>.", @@ -195,13 +178,13 @@ "boards": "Tavlor", "board-view": "Tavelvy", "desktop-mode": " Skrivbordsläge", - "mobile-mode": "Mobilläge", + "mobile-mode": " Mobilläge", "mobile-desktop-toggle": " Växla mellan mobilläge och skrivbordsläge", "zoom-in": "Zooma in", "zoom-out": "Zooma ut", "click-to-change-zoom": " Klicka för att ändra zoomnivå", - "zoom-level": "Zoomnivå", - "enter-zoom-level": "Ange zoomnivå (50-300%):", + "zoom-level": " Zoomnivå", + "enter-zoom-level": " Ange zoomnivå (50-300%):", "board-view-cal": "Kalender", "board-view-swimlanes": "Simbanor", "board-view-collapse": "Fäll ihop", @@ -286,8 +269,6 @@ "change-permissions": "Ändra behörigheter", "change-settings": "Ändra inställningar", "changeAvatarPopup-title": "Ändra avatar", - "delete-avatar-confirm": "Är du säker på att du vill ta bort denna avatar?", - "deleteAvatarPopup-title": "Ta bort avatar?", "changeLanguagePopup-title": "Ändra språk", "changePasswordPopup-title": "Ändra lösenord", "changePermissionsPopup-title": "Ändra behörigheter", @@ -335,16 +316,10 @@ "comment-placeholder": "Skriv kommentar", "comment-only": "Kommentera endast", "comment-only-desc": "Kan endast kommentera kort.", - "comment-assigned-only": "Endast tilldelad kommentar", - "comment-assigned-only-desc": "Endast tilldelade kort synliga. Kan endast kommentera.", "comment-delete": "Är du säker på att du vill radera kommentaren?", "deleteCommentPopup-title": "Radera kommentaren?", "no-comments": "Inga kommentarer", - "no-comments-desc": "Kan inte se kommentarer.", - "read-only": "Skrivskyddad", - "read-only-desc": "Kan endast visa kort. Kan inte redigera.", - "read-assigned-only": "Endast tilldelad läsning", - "read-assigned-only-desc": "Endast tilldelade kort synliga. Kan inte redigera.", + "no-comments-desc": "Kan inte se kommentarer och aktiviteter.", "worker": "Arbetare", "worker-desc": "Kan endast flytta kort, tilldela sig själv till kort och kommentera.", "computer": "Dator", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Är du säker på att du vill radera checklistan?", "subtaskDeletePopup-title": "Radera deluppgift?", "checklistDeletePopup-title": "Radera checklistan?", - "checklistItemDeletePopup-title": "Radera checklistpost?", "copy-card-link-to-clipboard": "Kopiera kortlänk till urklipp", "copy-text-to-clipboard": "Kopiera text till urklipp", "linkCardPopup-title": "Länka kort", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Första kortets titel\", \"description\":\"Första kortets beskrivning\"}, {\"title\":\"Andra kortets titel\",\"description\":\"Andra kortets beskrivning\"},{\"title\":\"Sista kortets titel\",\"description\":\"Sista kortets beskrivning\"} ]", "create": "Skapa", "createBoardPopup-title": "Skapa tavla", - "createTemplateContainerPopup-title": "Lägg till från mall", "chooseBoardSourcePopup-title": "Importera tavla", "createLabelPopup-title": "Skapa etikett", "createCustomField": "Skapa fält", @@ -385,7 +358,7 @@ "date": "Datum", "date-format": "Datumformat", "date-format-yyyy-mm-dd": "ÅÅÅÅ-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-ÅÅÅÅ", + "date-format-dd-mm-yyyy": "DD-MM-ÅÅÅÅ", "date-format-mm-dd-yyyy": "MM-DD-ÅÅÅÅ", "decline": "Neka", "default-avatar": "Standard avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Du kan flytta en lista till Arkiv för att ta bort den från tavlan och bevara aktiviteten.", "lists": "Listor", "swimlanes": "Simbanor", - "calendar": "Kalender", - "gantt": "Gantt", "log-out": "Logga ut", "log-in": "Logga in", "loginPopup-title": "Logga in", "memberMenuPopup-title": "Användarinställningar", - "grey-icons": "Grå ikoner", "members": "Medlemmar", "menu": "Meny", "move-selection": "Flytta vald", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Flytta längst ner", "moveCardToTop-title": "Flytta högst upp", "moveSelectionPopup-title": "Flytta vald", - "copySelectionPopup-title": "Kopiera markering", - "selection-color": "Markeringsfärg", "multi-selection": "Flerval", "multi-selection-label": "Ange etikett för val", "multi-selection-member": "Ange medlem för val", @@ -587,8 +555,6 @@ "no-results": "Inga reslutat", "normal": "Normal", "normal-desc": "Kan se och redigera kort. Kan inte ändra inställningar.", - "normal-assigned-only": "Endast tilldelad normal", - "normal-assigned-only-desc": " Endast tilldelade kort synliga. Redigera som normal användare.", "not-accepted-yet": "Inbjudan inte ännu accepterad", "notify-participate": "Få uppdateringar på alla kort du är med som deltagare, skapare eller medlem", "notify-watch": "Få uppdateringar till alla anslagstavlor, listor, eller kort du bevakar", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Tillåt e-poständring", "accounts-allowUserNameChange": "Tillåt användarnamnändring", "tableVisibilityMode-allowPrivateOnly": "Tavlans synlighet: Tillåt enbart privata tavlor", - "tableVisibilityMode": "Tavlans synlighet", + "tableVisibilityMode" : "Tavlans synlighet", "createdAt": "Skapad vid", "modifiedAt": "Senast ändrad", "verified": "Verifierad", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Ändra mottagningsdatum", "editCardEndDatePopup-title": "Ändra slutdatum", "setCardColorPopup-title": "Ange färg", - "setSelectionColorPopup-title": "Ange markeringsfärg", "setCardActionsColorPopup-title": "Välj en färg", "setSwimlaneColorPopup-title": "Välj en färg", "setListColorPopup-title": "Välj en färg", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Alla listor, kort, etiketter och aktiviteter kommer tas bort och du kommer inte kunna återställa tavlans innehåll. Det går inte att ångra.", "boardDeletePopup-title": "Ta bort tavla?", "delete-board": "Ta bort tavla", - "delete-all-notifications": "Ta bort alla notifieringar", - "delete-all-notifications-confirm": "Är du säker på att du vill ta bort alla notifieringar? Denna åtgärd kan inte ångras.", "delete-duplicate-lists": "Ta bort dubblettlistor", "delete-duplicate-lists-confirm": "Är du säker? Detta kommer att ta bort alla dubblettlistor som har samma namn och inte innehåller några kort.", "default-subtasks-board": "Deluppgifter för __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Autentiseringsmetod", "authentication-type": "Autentiseringstyp", "custom-product-name": "Anpassat produktnamn", - "custom-head-tags-enabled": "Aktivera anpassade head-taggar", - "custom-head-meta-tags": "Anpassade meta-taggar (HTML)", - "custom-head-link-tags": "Anpassade link-taggar (HTML)", - "custom-manifest-enabled": "Aktivera anpassat webbmanifest", - "custom-head-manifest-content": " Anpassat webbmanifestinnehåll (JSON)", - "custom-assetlinks-enabled": "Aktivera anpassad assetlinks.json", - "custom-assetlinks-content": " Anpassat assetlinks.json-innehåll (JSON)", "layout": "Layout", "hide-logo": "Dölj logotypen", "hide-card-counter-list": "Göm kort-räknar listan på alla tavlor", @@ -979,8 +935,6 @@ "a-endAt": "ändrad sluttid att vara", "a-startAt": "ändrad starttid att vara", "a-receivedAt": "ändrad mottagen tid att vara", - "above-selected-card": "Ovanför markerat kort", - "below-selected-card": "Under markerat kort", "almostdue": "aktuell förfallotid %s närmar sig", "pastdue": "aktuell förfallotid %s är förbi", "duenow": "aktuell förfallotid %s är idag", @@ -989,7 +943,7 @@ "act-almostdue": "påminde om den aktuella förfallotiden (__timeValue__) av __card__ närmar sig", "act-pastdue": "påminde om den aktuella förfallotiden (__timeValue__) av __card__ är förbi", "act-duenow": "påminde om den aktuella förfallotiden (__timeValue__) av __card__ är nu", - "act-atUserComment": "nämnde dig i kort __card__: __comment__ i lista __list__ i simbana __swimlane__ på tavla __board__", + "act-atUserComment": "Du omnämndes i [__board__] __list__/__card__", "delete-user-confirm-popup": "Är du säker på att du vill ta bort det här kontot? Det går inte att ångra sig.", "delete-team-confirm-popup": "Är du säker på att du vill ta bort det är teamet? Det går inte ångra sig.", "delete-org-confirm-popup": "Är du säker att du vill radera denna organisationen? Det går inte att ångra.", @@ -1013,7 +967,6 @@ "view-all": "Visa allt", "filter-by-unread": "Filtrera efter oläst", "mark-all-as-read": "Markera alla som lästa", - "mark-all-as-unread": "Markera alla som olästa", "remove-all-read": "Ta bort alla lästa", "allow-rename": "Tillåt Namnändring", "allowRenamePopup-title": "Tillåt namnändring", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "Mina kort", "card": "Kort", - "today": "Idag", - "day": "Dag", - "week": "Vecka", - "month": "Månad", "list": "Lista", "board": "Tavla", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Dölj alla objekt i checklistan", "support": "Hjälp", "supportPopup-title": "Hjälp", - "support-page-enabled": "Supportsida aktiverad", - "support-info-not-added-yet": " Supportinformation har inte lagts till ännu", - "support-info-only-for-logged-in-users": " Supportinformation är endast för inloggade användare.", - "support-title": "Supporttitel", - "support-content": " Supportinnehåll", "accessibility": " Tillgänglighet", "accessibility-page-enabled": "Tillgänglighetssida aktiverad", "accessibility-info-not-added-yet": " Tillgänglighetsinformation har inte lagts till ännu", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Schemalagt jobb borttaget", "cron-job-pause-failed": "Misslyckades med att pausa schemalagt jobb", "cron-job-paused": "Schemalagt jobb pausat", - "cron-job-resume-failed": "Misslyckades återuppta schemalagt jobb", - "cron-job-resumed": "Schemalagt jobb återupptaget framgångsrikt", - "cron-job-start-failed": "Misslyckades starta schemalagt jobb", - "cron-job-started": "Schemalagt jobb startat framgångsrikt", - "cron-migration-errors": "Migreringsfel", - "cron-migration-warnings": "Migreringsvarningar", - "cron-no-errors": "Inga fel att visa", - "cron-error-severity": "Allvarlighetsgrad", - "cron-error-time": "Tid", - "cron-error-message": "Felmeddelande", - "cron-error-details": "Detaljer", - "cron-clear-errors": "Rensa alla fel", - "cron-retry-failed": "Försök misslyckade migreringar igen", - "cron-resume-paused": "Återuppta pausade migreringar", - "cron-errors-cleared": "Alla fel rensade framgångsrikt", - "cron-no-failed-migrations": "Inga misslyckade migreringar att försöka igen", - "cron-no-paused-migrations": "Inga pausade migreringar att återuppta", - "cron-migrations-resumed": "Migreringar återupptagna framgångsrikt", - "cron-migrations-retried": "Misslyckade migreringar försökta igen framgångsrikt", - "complete": "Avslutad", - "idle": "Inaktiv", "filesystem-path-description": "Basväg för fillagring", "gridfs-enabled": "GridFS aktiverat", "gridfs-enabled-description": "Använd MongoDB GridFS för fillagring", - "all-migrations": "Alla migreringar", - "select-migration": "Välj migrering", - "start": "Påbörjades", - "pause": "Pause", - "stop": "Stopp", - "migration-starting": "Startar migreringar...", - "migration-pausing": "Pausar migreringar...", - "migration-stopping": "Stoppar migreringar...", "migration-pause-failed": "Misslyckades med att pausa migreringar", "migration-paused": "Migreringar har pausats", "migration-progress": "Migreringsförlopp", "migration-start-failed": "Misslyckades med att starta migreringar", "migration-started": "Migreringar har startats", - "migration-not-needed": "Ingen migrering behövs", "migration-status": "Migreringsstatus", "migration-stop-confirm": "Är du säker på att du vill stoppa alla migreringar?", "migration-stop-failed": "Misslyckades med att stoppa migreringar", @@ -1484,94 +1398,28 @@ "add-job": "Lägg till jobb", "attachment-migration": "Migrering av bilagor", "attachment-monitoring": "Övervakning av bilagor", - "attachment-settings": "Bilageinställningar", - "attachment-storage-settings": " Lagringsinställningar", - "automatic-migration": "Automatisk migrering", - "back-to-settings": "Tillbaka till inställningar", - "board-id": "Tavlans ID", - "board-migration": "Tavelmigration", - "board-migrations": "Migrering av tavlor", - "card-show-lists-on-minicard": "Visa listor på minikort", - "comprehensive-board-migration": "Fullständig migration av alla tavlor ", - "comprehensive-board-migration-description": "Utför omfattande kontroller och korrigeringar för tavlans dataintegritet, inklusive listordning, kortpositioner och swimlane-struktur.", - "delete-duplicate-empty-lists-migration": "Ta bort duplicerade tomma listor", - "delete-duplicate-empty-lists-migration-description": "Tar säkert bort tomma duplicerade listor. Tar endast bort listor som inte har några kort OCH där det finns en annan lista med samma titel som innehåller kort.", - "lost-cards": "Förlorade kort ", - "lost-cards-list": "Återställda objekt", - "restore-lost-cards-migration": "Återställ förlorade kort", - "restore-lost-cards-migration-description": "Hittar och återställer kort och listor som saknar simbaneid eller listId. Skapar en 'Förlorade kort'-simbana för att göra alla förlorade objekt synliga igen.", - "restore-all-archived-migration": "Återställ från Arkiv", - "restore-all-archived-migration-description": "Återställer alla arkiverade swimlanes, listor och kort. Korrigerar automatiskt alla saknade simbaneId eller listId för att göra objekt synliga.", - "fix-missing-lists-migration": "Fixa saknade listor", - "fix-missing-lists-migration-description": "Upptäcker och reparerar saknade eller korrupta listor i tavlans struktur.", - "fix-avatar-urls-migration": "Fixa avatar-URL:er", - "fix-avatar-urls-migration-description": "Uppdaterar avatar-URL:er för tavlans medlemmar till att använda rätt lagrings-backend och fixar trasiga avatar-referenser.", - "fix-all-file-urls-migration": "Fixa alla fil-URL:er", - "fix-all-file-urls-migration-description": "Uppdaterar alla URL:er för filbilagor på denna tavla till att använda rätt lagrings-backend och fixar trasiga filreferenser.", - "migration-needed": "Migrering krävs", - "migration-complete": "Avslutad", - "migration-running": "Körs...", - "migration-successful": "Migrering slutförd", - "migration-failed": "Migrering misslyckades", - "migrations": "Migreringar", - "migrations-admin-only": "Endast tavlans administratörer kan köra migreringar", - "migrations-description": "Kör dataintegritetskontroller och reparationer för denna tavla. Varje migrering kan utföras individuellt.", - "no-issues-found": "Inga problem hittades", - "run-migration": "Kör migrering", - "run-comprehensive-migration-confirm": "Detta kommer att utföra en omfattande migrering för att kontrollera och fixa tavlans dataintegritet. Detta kan ta en liten stund. Fortsätt?", - "run-delete-duplicate-empty-lists-migration-confirm": "Detta kommer först att konvertera alla delade listor till listor per simbana, och sedan ta bort tomma listor som har en dubblettlista med samma titel som innehåller kort. Endast överflödiga tomma listor kommer att tas bort. Fortsätt?", - "run-restore-lost-cards-migration-confirm": "Detta kommer att skapa en 'Förlorade kort'-simbana och återställa alla kort och listor som saknar simbaneId eller listId. Detta påverkar endast icke-arkiverade objekt. Fortsätt?", - "run-restore-all-archived-migration-confirm": "Detta kommer att återställa ALLA arkiverade simbanor, listor och kort, vilket gör dem synliga igen. Alla objekt med saknade ID:n kommer att fixas automatiskt. Detta kan inte enkelt ångras. Fortsätt?", - "run-fix-missing-lists-migration-confirm": "Detta kommer att upptäcka och reparera saknade eller korrupta listor i tavlans struktur. Fortsätt?", - "run-fix-avatar-urls-migration-confirm": "Detta kommer att uppdatera avatar-URL:er för tavlans medlemmar till att använda rätt lagrings-backend. Fortsätt?", - "run-fix-all-file-urls-migration-confirm": "Detta kommer att uppdatera alla URL:er för filbilagor på denna tavla till att använda rätt lagrings-backend. Fortsätt?", - "restore-lost-cards-nothing-to-restore": "Inga förlorade simbanor, listor eller kort att återställa", - - "migration-progress-title": "Tavlans migrering pågår", - "migration-progress-overall": "Övergripande förlopp", - "migration-progress-current-step": "Nuvarande steg", - "migration-progress-status": "Status", - "migration-progress-details": "Detaljer", - "migration-progress-note": "Vänta medan vi migrerar din tavla till den senaste strukturen...", - "steps": "steg", - "view": "Visa", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analysera tavlans struktur", - "step-fix-orphaned-cards": "Fixa övergivna kort", - "step-convert-shared-lists": "Konvertera delade listor", - "step-ensure-per-swimlane-lists": "Säkerställ listor per simbana", - "step-validate-migration": "Validera migrering", - "step-fix-avatar-urls": "Fixa avatar-URL:er", - "step-fix-attachment-urls": "Fixa bilage-URL:er", - "step-analyze-lists": "Analysera listor", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Uppdatera kort", - "step-finalize": "Slutför", - "step-delete-duplicate-empty-lists": "Ta bort duplicerade tomma listor", - "step-ensure-lost-cards-swimlane": "Säkerställ simbana för förlorade kort", - "step-restore-lists": "Återställ listor", - "step-restore-cards": "Återställ kort", - "step-restore-swimlanes": "Återställ simbanor", - "step-fix-missing-ids": "Fixa saknade ID:n", - "step-scan-users": "Kontrollerar tavlans medlemsavatarer", - "step-scan-files": "Kontrollerar tavlans filbilagor", - "step-fix-file-urls": "Fixar fil-URL:er", - "cleanup": "Rensning", - "cleanup-old-jobs": "Rensa gamla jobb", + "attachment-settings": "Attachment Settings", + "attachment-storage-settings": "Storage Settings", + "automatic-migration": "Automatic Migration", + "back-to-settings": "Back to Settings", + "board-id": "Board ID", + "board-migration": "Board Migration", + "card-show-lists-on-minicard": "Show Lists on Minicard", + "cleanup": "Cleanup", + "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Avslutad", - "conversion-info-text": "Denna konvertering utförs en gång per tavla och förbättrar prestandan. Ni kan fortsätta använda tavlan som vanligt.", - "converting-board": "Konverterar tavla", - "converting-board-description": "Konverterar tavlans struktur för förbättrad funktionalitet. Detta kan ta en liten stund.", - "cpu-cores": "CPU-kärnor", - "cpu-usage": "CPU-användning", - "current-action": "Nuvarande åtgärd", - "database-migration": "Databas­migrering", - "database-migration-description": "Uppdaterar databasstrukturen för förbättrad funktionalitet och prestanda. Denna process kan ta flera minuter.", - "database-migrations": "Databas­migreringar", - "days-old": "Dagar gammal", - "duration": "Varaktighet", - "errors": "Fel", + "conversion-info-text": "This conversion is performed once per board and improves performance. You can continue using the board normally.", + "converting-board": "Converting Board", + "converting-board-description": "Converting board structure for improved functionality. This may take a few moments.", + "cpu-cores": "CPU Cores", + "cpu-usage": "CPU Usage", + "current-action": "Current Action", + "database-migration": "Database Migration", + "database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.", + "database-migrations": "Database Migrations", + "days-old": "Days Old", + "duration": "Duration", + "errors": "Errors", "estimated-time-remaining": "Beräknad återstående tid", "every-1-day": "Var 1 dag", "every-1-hour": "Var 1 timme", @@ -1580,107 +1428,76 @@ "every-30-minutes": "Var 30 minuter", "every-5-minutes": "Var 5 minuter", "every-6-hours": "Var 6 timmar", - "export-monitoring": "Exportövervakning", - "filesystem-attachments": "Filssystem-bilagor", - "filesystem-size": "Filssystemstorlek", - "filesystem-storage": "Filssystemlagring", - "force-board-scan": "Tvinga tavelskanning", - "gridfs-attachments": "GridFS-bilagor", - "gridfs-size": "GridFS-storlek", + "export-monitoring": "Export Monitoring", + "filesystem-attachments": "Filesystem Attachments", + "filesystem-size": "Filesystem Size", + "filesystem-storage": "Filesystem Storage", + "force-board-scan": "Force Board Scan", + "gridfs-attachments": "GridFS Attachments", + "gridfs-size": "GridFS Size", "gridfs-storage": "GridFS", - "hide-list-on-minicard": "Göm lista på minikort", - "idle-migration": "Inaktiv migrering", - "job-description": "Jobbeskrivning", - "job-details": "Jobbdetaljer", - "job-name": "Jobbnamn", - "job-queue": "Jobbkö", - "last-run": "Senaste körning", - "max-concurrent": "Max samtidigt", - "memory-usage": "Minnesanvändning", - "migrate-all-to-filesystem": "Migrera allt till filsystem", - "migrate-all-to-gridfs": "Migrera allt till GridFS", - "migrate-all-to-s3": "Migrera allt till S3", - "migrated-attachments": "Migrerade bilagor", - "migration-batch-size": "Batchstorlek", - "migration-batch-size-description": "Antal bilagor att bearbeta i varje batch (1-100)", - "migration-cpu-threshold": "CPU-tröskel (%)", - "migration-cpu-threshold-description": "Pausa migreringen när CPU-användningen överstiger denna procentsats (10-90)", - "migration-delay-ms": "Fördröjning (ms)", - "migration-delay-ms-description": "Fördröjning mellan batchar i millisekunder (100-10000)", - "migration-detector": "Migreringsdetektor", - "migration-info-text": "Databas­migreringar utförs en gång och förbättrar systemets prestanda. Processen fortsätter i bakgrunden även om du stänger din webbläsare.", - "migration-log": "Migreringslogg", - "migration-markers": "Migreringsmarkörer", - "migration-resume-failed": "Misslyckades med att återuppta migrering", - "migration-resumed": "Migrering återupptogs", - "migration-steps": "Migreringssteg", - "migration-warning-text": "Vänligen stäng inte din webbläsare under migreringen. Processen fortsätter i bakgrunden men kan ta längre tid att slutföra.", - "monitoring-export-failed": "Misslyckades med att exportera övervakningsdata", - "monitoring-refresh-failed": "Misslyckades med att uppdatera övervakningsdata", - "next": "Nästa", - "next-run": "Nästa körning", + "hide-list-on-minicard": "Hide List on Minicard", + "idle-migration": "Idle Migration", + "job-description": "Job Description", + "job-details": "Job Details", + "job-name": "Job Name", + "job-queue": "Job Queue", + "last-run": "Last Run", + "max-concurrent": "Max Concurrent", + "memory-usage": "Memory Usage", + "migrate-all-to-filesystem": "Migrate All to Filesystem", + "migrate-all-to-gridfs": "Migrate All to GridFS", + "migrate-all-to-s3": "Migrate All to S3", + "migrated-attachments": "Migrated Attachments", + "migration-batch-size": "Batch Size", + "migration-batch-size-description": "Number of attachments to process in each batch (1-100)", + "migration-cpu-threshold": "CPU Threshold (%)", + "migration-cpu-threshold-description": "Pause migration when CPU usage exceeds this percentage (10-90)", + "migration-delay-ms": "Delay (ms)", + "migration-delay-ms-description": "Delay between batches in milliseconds (100-10000)", + "migration-detector": "Migration Detector", + "migration-info-text": "Database migrations are performed once and improve system performance. The process continues in the background even if you close your browser.", + "migration-log": "Migration Log", + "migration-markers": "Migration Markers", + "migration-resume-failed": "Failed to resume migration", + "migration-resumed": "Migration resumed", + "migration-steps": "Migration Steps", + "migration-warning-text": "Please do not close your browser during migration. The process will continue in the background but may take longer to complete.", + "monitoring-export-failed": "Failed to export monitoring data", + "monitoring-refresh-failed": "Failed to refresh monitoring data", + "next": "Next", + "next-run": "Next Run", "of": "av", - "operation-type": "Operationstyp", - "overall-progress": "Övergripande förlopp", - "page": "Sida", - "pause-migration": "Pausa migrering", - "previous": "Föregående", - "refresh": "Uppdatera", - "refresh-monitoring": "Uppdatera övervakning", - "remaining-attachments": "Återstående bilagor", - "resume-migration": "Återuppta migrering", - "run-once": "Kör en gång", - "s3-attachments": "S3-bilagor", - "s3-size": "S3-storlek", + "operation-type": "Operation Type", + "overall-progress": "Overall Progress", + "page": "Page", + "pause-migration": "Pause Migration", + "previous": "Previous", + "refresh": "Refresh", + "refresh-monitoring": "Refresh Monitoring", + "remaining-attachments": "Remaining Attachments", + "resume-migration": "Resume Migration", + "run-once": "Run once", + "s3-attachments": "S3 Attachments", + "s3-size": "S3 Size", "s3-storage": "S3", - "scanning-status": "Skanningsstatus", - "schedule": "Schema", - "search-boards-or-operations": "Sök tavlor eller operationer...", - "show-list-on-minicard": "Visa lista på minikort", - "showChecklistAtMinicard": "Visa checklista på minikort", - "showing": "Visar", - "start-test-operation": "Starta testoperation", - "start-time": "Starttid", - "step-progress": "Stegförlopp", - "stop-migration": "Stoppa migrering", - "storage-distribution": "Lagringsdistribution", - "system-resources": "Systemresurser", - "total-attachments": "Totala bilagor", - "total-operations": "Totala operationer", - "total-size": "Total storlek", - "unmigrated-boards": "Okonverterade tavlor", - "weight": "Vikt", - "cron": "Cron", - "current-step": "Nuvarande steg", - "otp": "OTP-kod", - "create-account": "Skapa konto", - "already-account": "Har du redan ett konto? Logga in", - "available-repositories": "Tillgängliga repositorier", - "repositories": "Repositorier", - "repository": "Repositorium", - "repository-name": "Repositorienamn", - "size-bytes": "Storlek (bytes)", - "last-modified": "Senast ändrad", - "no-repositories": "Inga repositorier hittades", - "create-repository": "Skapa repositorium", - "upload-repository": "Ladda upp/uppdatera repositorium", - "api-endpoints": "API-slutpunkter", - "sign-in-to-upload": " Logga in för att ladda upp repositorier", - "account-locked": "Kontot är tillfälligt låst på grund av för många misslyckade inloggningsförsök. Vänligen försök igen senare.", - "otp-required": "OTP-kod krävs", - "invalid-credentials": "Ogiltigt användarnamn eller lösenord", - "username-password-required": "Användarnamn och lösenord krävs", - "password-mismatch": "Lösenorden matchar inte", - "username-too-short": " Användarnamnet måste vara minst 3 tecken", - "user-exists": "Användaren finns redan", - "account-created": "Konto skapat! Du kan nu logga in.", - "account-creation-failed": "Misslyckades skapa konto", - "login": "Login", - "confirm": "Bekräfta", - "error": "Fel", - "file": "Fil", - "log": "Logg", - "logout": "Logga ut", - "server": "Server", - "protocol": "Protokoll" + "scanning-status": "Scanning Status", + "schedule": "Schedule", + "search-boards-or-operations": "Search boards or operations...", + "show-list-on-minicard": "Show List on Minicard", + "showing": "Showing", + "start-test-operation": "Start Test Operation", + "start-time": "Start Time", + "step-progress": "Step Progress", + "stop-migration": "Stop Migration", + "storage-distribution": "Storage Distribution", + "system-resources": "System Resources", + "total-attachments": "Total Attachments", + "total-operations": "Total Operations", + "total-size": "Total Size", + "unmigrated-boards": "Unmigrated Boards", + "weight": "Weight", + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/sw.i18n.json b/imports/i18n/data/sw.i18n.json index c5e35ca25..ae5c41a0d 100644 --- a/imports/i18n/data/sw.i18n.json +++ b/imports/i18n/data/sw.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Andika changio", "comment-only": "Changia pekee", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Tarakilishi", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ta.i18n.json b/imports/i18n/data/ta.i18n.json index 7b1ec1693..c2e38c9eb 100644 --- a/imports/i18n/data/ta.i18n.json +++ b/imports/i18n/data/ta.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "கடவுச்சொல்லை மாற்று", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "கருத்து மட்டும்", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "கருத்து இல்லை", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "கணினி", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "உருவாக்கு", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "நாள்", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "நாள்கட்டி", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/te-IN.i18n.json b/imports/i18n/data/te-IN.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/te-IN.i18n.json +++ b/imports/i18n/data/te-IN.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/th.i18n.json b/imports/i18n/data/th.i18n.json index 1dda8dc8f..fc992cb5d 100644 --- a/imports/i18n/data/th.i18n.json +++ b/imports/i18n/data/th.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "เพิ่มสมาชิก", "added": "เพิ่ม", - "addMemberPopup-title": "เพิ่มสมาชิก", + "addMemberPopup-title": "สมาชิก", "memberPopup-title": "การตั้งค่า", "admin": "ผู้ดูแลระบบ", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "สามารถดูและแก้ไขการ์ด ลบสมาชิก และเปลี่ยนการตั้งค่าบอร์ดได้", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "ติดดาว %s", "board-not-found": "ไม่มีบอร์ด", "board-private-info": "บอร์ดนี้จะเป็น <strong>ส่วนตัว</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "เปลี่ยนสิทธิ์", "change-settings": "เปลี่ยนการตั้งค่า", "changeAvatarPopup-title": "เปลี่ยนภาพ", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "เปลี่ยนภาษา", "changePasswordPopup-title": "เปลี่ยนรหัสผ่าน", "changePermissionsPopup-title": "เปลี่ยนสิทธิ์", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "คอมพิวเตอร์", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "สร้าง", "createBoardPopup-title": "สร้างบอร์ด", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "สร้างป้ายกำกับ", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "วันที่", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "ปฎิเสธ", "default-avatar": "ภาพเริ่มต้น", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "รายการ", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "ออกจากระบบ", "log-in": "เข้าสู่ระบบ", "loginPopup-title": "เข้าสู่ระบบ", "memberMenuPopup-title": "การตั้งค่า", - "grey-icons": "Grey Icons", "members": "สมาชิก", "menu": "เมนู", "move-selection": "เลือกย้าย", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "ย้ายไปล่าง", "moveCardToTop-title": "ย้ายไปบน", "moveSelectionPopup-title": "เลือกย้าย", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "เลือกหลายรายการ", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "ไม่มีข้อมูล", "normal": "ธรรมดา", "normal-desc": "สามารถดูและแก้ไขการ์ดได้ แต่ไม่สามารถเปลี่ยนการตั้งค่าได้", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "ยังไม่ยอมรับคำเชิญ", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "ได้รับการแจ้งปรับปรุงบอร์ด รายการหรือการ์ดที่คุณเฝ้าติดตาม", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "เวลา", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "เริ่ม", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/tk_TM.i18n.json b/imports/i18n/data/tk_TM.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/tk_TM.i18n.json +++ b/imports/i18n/data/tk_TM.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/tlh.i18n.json b/imports/i18n/data/tlh.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/tlh.i18n.json +++ b/imports/i18n/data/tlh.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/tr.i18n.json b/imports/i18n/data/tr.i18n.json index 4b094d8b1..a4d61f39c 100644 --- a/imports/i18n/data/tr.i18n.json +++ b/imports/i18n/data/tr.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "%s yorum silindi", "activity-receivedDate": "alma tarihi için düzenlendi%s", "activity-startDate": "başlangıç tarihi %s, %s olarak düzenlendi", - "allboards.starred": "Starred", - "allboards.templates": "Şablonlar", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "bitiş tarihi %s / %s olarak düzenlendi", "activity-endDate": "son bitiş tarihi %s, %s olarak düzenlendi", "add-attachment": "Ek Ekle", @@ -98,7 +86,6 @@ "add-card": "Kart Ekle", "add-card-to-top-of-list": "Listenin Başına Kart Ekle", "add-card-to-bottom-of-list": "Listenin Sonuna Kart Ekle", - "addListPopup-title": "Liste Ekle", "setListWidthPopup-title": "Genişlik Ata", "set-list-width": "Genişlik Ata", "set-list-width-value": "En Az & En Çok Genişlik (piksel) Ata ", @@ -122,10 +109,10 @@ "add-after-list": "Listeden Sonra Ekle", "add-members": "Üye ekle", "added": "Eklendi", - "addMemberPopup-title": "Üye ekle", + "addMemberPopup-title": "Üyeler", "memberPopup-title": "Üye Ayarları", "admin": "Yönetici", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Kartları görüntüleyebilir ve düzenleyebilir, üyeleri çıkarabilir ve pano ayarlarını değiştirebilir.", "admin-announcement": "Duyuru", "admin-announcement-active": "Tüm Sistemde Etkin Duyuru", "admin-announcement-title": "Yöneticiden Duyuru", @@ -167,16 +154,12 @@ "board-background-image-url": "Arkaplan Resmi URL", "add-background-image": "Arkaplan Resmi Ekle", "remove-background-image": "Arkaplan Resmini Kaldır", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s yıldız", "board-not-found": "Pano bulunamadı", "board-private-info": "Bu pano <strong>özel</strong> olacak.", @@ -286,8 +269,6 @@ "change-permissions": "İzinleri değiştir", "change-settings": "Ayarları değiştir", "changeAvatarPopup-title": "Avatar Değiştir", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Dil Değiştir", "changePasswordPopup-title": "Parola Değiştir", "changePermissionsPopup-title": "Yetkileri Değiştirme", @@ -335,16 +316,10 @@ "comment-placeholder": "Yorum Yaz", "comment-only": "Sadece yorum", "comment-only-desc": "Sadece kartlara yorum yazabilir.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Yorumu silmek istediğinizden emin misiniz?", "deleteCommentPopup-title": "Yorum silinsin mi?", "no-comments": "Yorum Yok", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Yorumlar ve aktiviteleri göremiyorum.", "worker": "Çalışan", "worker-desc": "Yalnızca kartları taşıyabilir, kendisini karta atayabilir ve yorum yapabilir.", "computer": "Bilgisayar", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Kontrol listesini silmek istediğinizden emin misiniz?", "subtaskDeletePopup-title": "Alt Görev Silinsin mi?", "checklistDeletePopup-title": "Kontrol Listesi Silinsin mi?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Kartın linkini kopyala", "copy-text-to-clipboard": "Yazıyı kopyala", "linkCardPopup-title": "Bağlantı kartı", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"İlk kart başlığı\", \"description\":\"İlk kart açıklaması\"}, {\"title\":\"İkinci kart başlığı\",\"description\":\"İkinci kart açıklaması\"},{\"title\":\"Son kart başlığı\",\"description\":\"Son kart açıklaması\"} ]", "create": "Oluştur", "createBoardPopup-title": "Pano Oluşturma", - "createTemplateContainerPopup-title": "Şablon Konteyner Ekle", "chooseBoardSourcePopup-title": "Panoyu içe aktar", "createLabelPopup-title": "Etiket Oluşturma", "createCustomField": "Alanı yarat", @@ -385,7 +358,7 @@ "date": "Tarih", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Reddet", "default-avatar": "Varsayılan avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Bir listeyi panodan kaldırmak için Arşive taşıyabilir ve kayıtları saklayabilirsiniz.", "lists": "Listeler", "swimlanes": "Kulvarlar", - "calendar": "Takvim", - "gantt": "Gant Şeması", "log-out": "Oturum Kapat", "log-in": "Oturum Aç", "loginPopup-title": "Oturum Aç", "memberMenuPopup-title": "Üye Ayarları", - "grey-icons": "Grey Icons", "members": "Üyeler", "menu": "Menü", "move-selection": "Seçimi taşı", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Aşağı taşı", "moveCardToTop-title": "Yukarı taşı", "moveSelectionPopup-title": "Seçimi taşı", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Çoklu seçim", "multi-selection-label": "Seçim için etiket belirle", "multi-selection-member": "Seçim için üye belirle", @@ -587,8 +555,6 @@ "no-results": "Sonuç yok", "normal": "Normal", "normal-desc": "Kartları görüntüleyebilir ve düzenleyebilir. Ayarları değiştiremez.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Davet henüz kabul edilmemiş", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Takip ettiğiniz tüm pano, liste ve kartlar hakkında bildirim al", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "E-posta Değiştirmeye İzin Ver", "accounts-allowUserNameChange": "Kullanıcı adı değiştirmeye izin ver", "tableVisibilityMode-allowPrivateOnly": "Tahta Görünürlüğü: Sadece özel tahtalara izin ver", - "tableVisibilityMode": "Tahta Görünürlüğü", + "tableVisibilityMode" : "Tahta Görünürlüğü", "createdAt": "Oluşturulma tarihi", "modifiedAt": "Değiştirilme zamanı", "verified": "Doğrulanmış", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Giriş tarihini değiştir", "editCardEndDatePopup-title": "Bitiş tarihini değiştir", "setCardColorPopup-title": "Renk ayarla", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Renk seçimi yap", "setSwimlaneColorPopup-title": "Renk seçimi yap", "setListColorPopup-title": "Renk seçimi yap", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Tüm listeler, kartlar, etiketler ve etkinlikler silinecek ve pano içeriğini kurtaramayacaksınız. Geri dönüş yok.", "boardDeletePopup-title": "Panoyu Sil?", "delete-board": "Panoyu Sil", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "__board__ panosu için alt görevler", @@ -942,13 +905,6 @@ "authentication-method": "Kimlik doğrulama yöntemi", "authentication-type": "Kimlik doğrulama türü", "custom-product-name": "Özel Ürün Adı", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Düzen", "hide-logo": "Logoyu Gizle", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "bitiş zamanı değiştirildi", "a-startAt": "başlangıç zamanı değiştirildi", "a-receivedAt": "alınma zamanı değiştirildi", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "%s'in vadesi yaklaşıyor", "pastdue": "%s'in vadesi geçti", "duenow": "%s'in vadesi bugün", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "[__board__] __list__/__card__ içinde bahsedildiniz", "delete-user-confirm-popup": "Bu kullanıcı hesabını silmek istediğinize emin misiniz? Bu işlemi geri alamazsınız.", "delete-team-confirm-popup": "Bu takımı silmek istediğinizden emin misiniz? Geri alma yok.", "delete-org-confirm-popup": "Bu kuruluşu silmek istediğinizden emin misiniz? Geri alma yok.", @@ -1013,7 +967,6 @@ "view-all": "Tümünü gör", "filter-by-unread": "Okunmamışlara göre filtrele", "mark-all-as-read": "Tümünü okundu olarak işaretle", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Okunanların tümünü kaldır", "allow-rename": "Yeniden Adlandırmaya İzin Ver", "allowRenamePopup-title": "Yeniden Adlandırmaya İzin Ver", @@ -1048,10 +1001,6 @@ "person": "Kişi", "my-cards": "Kartlarım", "card": "Kart", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Liste", "board": "Pano", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Destek", "supportPopup-title": "Destek", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Erişilebilirlik ", "accessibility-page-enabled": "Erişilebilirlik sayfası etkin", "accessibility-info-not-added-yet": "Erişilebilirlik bilgisi henüz eklenmedi", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Zaman", - "cron-error-message": "Error Message", - "cron-error-details": "Detaylar", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Başlama", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Durum", - "migration-progress-details": "Detaylar", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Tamamlandı", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Onayla", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ug.i18n.json b/imports/i18n/data/ug.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/ug.i18n.json +++ b/imports/i18n/data/ug.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/uk-UA.i18n.json b/imports/i18n/data/uk-UA.i18n.json index 062d11ee6..037a7a3e5 100644 --- a/imports/i18n/data/uk-UA.i18n.json +++ b/imports/i18n/data/uk-UA.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "видалено коментар %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Шаблони", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Додати вкладення", @@ -98,7 +86,6 @@ "add-card": "Додати картку", "add-card-to-top-of-list": "Додати картку на початок списку", "add-card-to-bottom-of-list": "Додати картку у кінець списку", - "addListPopup-title": "Додати список", "setListWidthPopup-title": "Встановити ширини", "set-list-width": "Встановити ширини", "set-list-width-value": "Встановити мін. та макс. ширину списку (у пікселях)", @@ -122,10 +109,10 @@ "add-after-list": "Додати після списку", "add-members": "Додати учасників", "added": "Додано", - "addMemberPopup-title": "Додати учасників", + "addMemberPopup-title": "Учасники", "memberPopup-title": "Налаштування учасників", "admin": "Адміністратор", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Може переглядати і редагувати картки, видаляти учасників та змінювати налаштування для дошки.", "admin-announcement": "Оголошення", "admin-announcement-active": "Активне системне оголошення", "admin-announcement-title": "Оголошення адміністратора", @@ -167,16 +154,12 @@ "board-background-image-url": "Посилання на фонове зображення", "add-background-image": "Додати фонове зображення", "remove-background-image": "Видалити фонове зображення", - "show-at-all-boards-page": "Показувати на сторінці всіх дошок", - "board-info-on-my-boards": "Налаштування всіх дошок", - "boardInfoOnMyBoardsPopup-title": "Налаштування всіх дошок", + "show-at-all-boards-page" : "Показувати на сторінці всіх дошок", + "board-info-on-my-boards" : "Налаштування всіх дошок", + "boardInfoOnMyBoardsPopup-title" : "Налаштування всіх дошок", "boardInfoOnMyBoards-title": "Налаштування всіх дошок", "show-card-counter-per-list": "Показувати кількість карток у списку", "show-board_members-avatar": "Показати аватари учасників дошки", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s зірок", "board-not-found": "Дошка не знайдена", "board-private-info": "Ця дошка буде <strong>приватною</strong>.", @@ -197,10 +180,10 @@ "desktop-mode": "Desktop Mode", "mobile-mode": "Mobile Mode", "mobile-desktop-toggle": "Toggle between Mobile and Desktop Mode", - "zoom-in": "Збільшити", - "zoom-out": "Зменшити", - "click-to-change-zoom": "Натисніть щоб змінити масштабування", - "zoom-level": "Масштабування", + "zoom-in": "Zoom In", + "zoom-out": "Zoom Out", + "click-to-change-zoom": "Click to change zoom level", + "zoom-level": "Zoom Level", "enter-zoom-level": "Enter zoom level (50-300%):", "board-view-cal": "Календар", "board-view-swimlanes": "Свімлейни", @@ -208,8 +191,8 @@ "board-view-gantt": "Гантт", "board-view-lists": "Списки", "bucket-example": "Like \"Bucket List\" for example", - "calendar-previous-month-label": "Попередній місяць", - "calendar-next-month-label": "Наступний місяць", + "calendar-previous-month-label": "Previous Month", + "calendar-next-month-label": "Next Month", "cancel": "Скасувати", "card-archived": "Цю картку переміщено до архіву.", "board-archived": "Цю дошку переміщено до архіву.", @@ -286,8 +269,6 @@ "change-permissions": "Змінити права доступу", "change-settings": "Змінити налаштування", "changeAvatarPopup-title": "Змінити аватар", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Змінити мову", "changePasswordPopup-title": "Змінити пароль", "changePermissionsPopup-title": "Змінити права доступу", @@ -335,16 +316,10 @@ "comment-placeholder": "Написати коментар", "comment-only": "Тільки коментарі", "comment-only-desc": "Може коментувати тільки картки.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Ви впевнені, що хочете видалити коментар?", "deleteCommentPopup-title": "Видалити коментар?", "no-comments": "Немає коментарів", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Не може бачити коментарі та активність.", "worker": "Робітник", "worker-desc": "Може тільки переміщати картки, призначати себе до картки і коментувати.", "computer": "Комп'ютер", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Ви впевнені, що хочете видалити контрольний список?", "subtaskDeletePopup-title": "Видалити підзадачу?", "checklistDeletePopup-title": "Видалити контрольний список?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Скопіювати посилання на картку в буфер обміну", "copy-text-to-clipboard": "Скопіювати текст у буфер обміну", "linkCardPopup-title": "Зв'язати картку", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[{\"title\": \"Перший заголовок картки\", \"description\":\"Перший опис картки\"}, {\"title\":\"Другий заголовок картки\",\"description\":\"Другий опис картки\"},{\"title\":\"Останній заголовок картки\",\"description\":\"Останній опис картки\"} ]", "create": "Створити", "createBoardPopup-title": "Створити дошку", - "createTemplateContainerPopup-title": "Додати шаблон контейнера", "chooseBoardSourcePopup-title": "Імпортувати дошку", "createLabelPopup-title": "Створити мітку", "createCustomField": "Створити поле", @@ -383,10 +356,10 @@ "custom-field-text": "Текст", "custom-fields": "Налаштовані поля", "date": "Дата", - "date-format": "Формат дати", - "date-format-yyyy-mm-dd": "РРРР-ММ-ДД", - "date-format-dd-mm-yyyy": "ДД-ММ-РРРР", - "date-format-mm-dd-yyyy": "ММ-ДД-РРРР", + "date-format": "Date Format", + "date-format-yyyy-mm-dd": "YYYY-MM-DD", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Відхилити", "default-avatar": "Аватар за замовчуванням", "delete": "Видалити", @@ -412,7 +385,7 @@ "editNotificationPopup-title": "Редагувати сповіщення", "editProfilePopup-title": "Редагувати профіль", "email": "Email", - "email-address": "Адреса електронної пошти", + "email-address": "Email Address", "email-enrollAccount-subject": "Акаунт створений для вас на __siteName__", "email-enrollAccount-text": "Привіт __user__,\n\nЩоб почати користуватися сервісом, просто натисніть на посилання нижче.\n\n__url__\n\nДякуємо.", "email-fail": "Невдача при відправленні email", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Ви можете перемістити список до архіву, щоб прибрати його з дошки та зберегти активність.", "lists": "Списки", "swimlanes": "Свімлейни", - "calendar": "Календар", - "gantt": "Гантт", "log-out": "Вийти", "log-in": "Увійти", "loginPopup-title": "Увійти", "memberMenuPopup-title": "Налаштування користувачів", - "grey-icons": "Grey Icons", "members": "Користувачі", "menu": "Меню", "move-selection": "Перенести вибране", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Перемістити на низ", "moveCardToTop-title": "Перемістити на початок", "moveSelectionPopup-title": "Перенести вибране", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Мультивибір", "multi-selection-label": "Встановити мітку для вибору", "multi-selection-member": "Встановити учасника для вибору", @@ -587,8 +555,6 @@ "no-results": "Немає результатів", "normal": "Звичайний", "normal-desc": "Може переглядати та редагувати картки. Не може змінювати налаштування.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Запрошення ще не прийнято", "notify-participate": "Отримувати оновлення по будь-яких картках, де ви берете участь як творець або учасник", "notify-watch": "Отримувати оновлення по будь-яких дошках, списках або картках, за якими ви спостерігаєте", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Дозволити зміну Email", "accounts-allowUserNameChange": "Дозволити зміну імені користувача", "tableVisibilityMode-allowPrivateOnly": "Видимість дошок: дозволити тільки приватні дошки", - "tableVisibilityMode": "Видимість дошок", + "tableVisibilityMode" : "Видимість дошок", "createdAt": "Створено", "modifiedAt": "Змінено", "verified": "Перевірено", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Змінити дату отримання", "editCardEndDatePopup-title": "Змінити дату закінчення", "setCardColorPopup-title": "Встановити колір", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Вибрати колір", "setSwimlaneColorPopup-title": "Вибрати колір", "setListColorPopup-title": "Вибрати колір", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Усі списки, картки, мітки та діяльність будуть видалені, і ви не зможете відновити вміст дошки. Немає відкату.", "boardDeletePopup-title": "Видалити дошку?", "delete-board": "Видалити дошку", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Підзадачі для дошки __board__", @@ -942,13 +905,6 @@ "authentication-method": "метод автентифікації", "authentication-type": "тип автентифікації", "custom-product-name": "Назва спеціального продукту", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Макет", "hide-logo": "Сховати логотип", "hide-card-counter-list": "Сховати лічильник карток на Всіх дошках", @@ -979,8 +935,6 @@ "a-endAt": "змінено час закінчення на", "a-startAt": "змінено час початку на", "a-receivedAt": "змінено час отримання на", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "поточний час завершення %s наближається", "pastdue": "поточний час завершення %s пройшов", "duenow": "поточний час завершення %s сьогодні", @@ -989,7 +943,7 @@ "act-almostdue": "нагадувало, що поточний термін (__timeValue__) __card__ наближається", "act-pastdue": "нагадувало, що поточний термін (__timeValue__) __card__ пройшов", "act-duenow": "нагадувало, що поточний термін (__timeValue__) __card__ зараз", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Вас згадали в [__board__] __list__/__card__", "delete-user-confirm-popup": "Ви дійсно бажаєте видалити цей обліковий запис? Цю дію не можна скасувати.", "delete-team-confirm-popup": "Ви впевнені, що хочете видалити цю команду? Немає можливості відмінити.", "delete-org-confirm-popup": "Ви впевнені, що хочете видалити цю організацію? Немає можливості відмінити.", @@ -1013,7 +967,6 @@ "view-all": "Показати все", "filter-by-unread": "Фільтрувати непрочитані", "mark-all-as-read": "Позначити всі як прочитані", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Видалити всі прочитані", "allow-rename": "Дозволити перейменування", "allowRenamePopup-title": "Дозволити перейменування", @@ -1048,10 +1001,6 @@ "person": "Особа", "my-cards": "Мої картки", "card": "Картка", - "today": "Сьогодні", - "day": "День", - "week": "Тиждень", - "month": "Місяць", "list": "Список", "board": "Дошка", "context-separator": "/", @@ -1341,39 +1290,34 @@ "hideAllChecklistItems": "Приховати всі пункти чек-листа", "support": "Підтримка", "supportPopup-title": "Підтримка", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", - "accessibility": "Доступність", + "accessibility": "Accessibility", "accessibility-page-enabled": "Доступность сторінки ввімкнена", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", "accessibility-title": "Accessibility title", "accessibility-content": "Доступность вмісту", - "accounts-lockout-settings": "Налаштування захисту від брутфорсу", + "accounts-lockout-settings": "Brute Force Protection Settings", "accounts-lockout-info": "These settings control how login attempts are protected against brute force attacks.", "accounts-lockout-known-users": "Settings for known users (correct username, wrong password)", "accounts-lockout-unknown-users": "Settings for unknown users (non-existent username)", - "accounts-lockout-failures-before": "Кількість невдалих спроб", - "accounts-lockout-period": "Період блокування (секунди)", + "accounts-lockout-failures-before": "Failures before lockout", + "accounts-lockout-period": "Lockout period (seconds)", "accounts-lockout-failure-window": "Failure window (seconds)", - "accounts-lockout-settings-updated": "Налаштування захисту від брутфорсу були оновлені", - "accounts-lockout-locked-users": "Заблоковані користувачі", + "accounts-lockout-settings-updated": "Brute force protection settings have been updated", + "accounts-lockout-locked-users": "Locked Users", "accounts-lockout-locked-users-info": "Users currently locked out due to too many failed login attempts", - "accounts-lockout-no-locked-users": "Нема заблокованих користувачів", - "accounts-lockout-failed-attempts": "Невдалих спроб", - "accounts-lockout-remaining-time": "Залишилось часу", - "accounts-lockout-user-unlocked": "Користувач був успішно розблокований", - "accounts-lockout-confirm-unlock": "Ви впевнені, що хочете розблокувати цього користувача?", - "accounts-lockout-confirm-unlock-all": "Ви впевнені, що хочете розблокувати усіх користувачів?", - "accounts-lockout-show-locked-users": "Показати тільки заблокованих користувачів", - "accounts-lockout-user-locked": "Користувач заблокований", - "accounts-lockout-click-to-unlock": "Натисніть, щоб розблокувати користувача", + "accounts-lockout-no-locked-users": "There are currently no locked users", + "accounts-lockout-failed-attempts": "Failed Attempts", + "accounts-lockout-remaining-time": "Remaining Time", + "accounts-lockout-user-unlocked": "User has been unlocked successfully", + "accounts-lockout-confirm-unlock": "Are you sure you want to unlock this user?", + "accounts-lockout-confirm-unlock-all": "Are you sure you want to unlock all locked users?", + "accounts-lockout-show-locked-users": "Show locked users only", + "accounts-lockout-user-locked": "User is locked", + "accounts-lockout-click-to-unlock": "Click to unlock this user", "accounts-lockout-status": "Статус", "admin-people-filter-show": "Show:", "admin-people-filter-all": "Усі Користувачі", - "admin-people-filter-locked": "Тільки заблоковані користувачі", + "admin-people-filter-locked": "Locked Users Only", "admin-people-filter-active": "Активно", "admin-people-filter-inactive": "Not Active", "admin-people-active-status": "Active Status", @@ -1403,50 +1347,20 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Час", - "cron-error-message": "Error Message", - "cron-error-details": "Деталі", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "Всі міграції", - "select-migration": "Оберіть міграцію", - "start": "Початок", - "pause": "Пауза", - "stop": "Стоп", - "migration-starting": "Початок міграцій...", - "migration-pausing": "Призупинення міграцій...", - "migration-stopping": "Зупинка міграцій...", - "migration-pause-failed": "Не вдалось призупинити міграції", - "migration-paused": "Міграції успішно призупинено", - "migration-progress": "Прогрес міграцій", - "migration-start-failed": "Не вдалось почати міграції", - "migration-started": "Міграції розпочалися успішно", - "migration-not-needed": "Міграції не потрібні", - "migration-status": "Статус міграцій", - "migration-stop-confirm": "Ви впевнені, що хочете зупинити всі міграції?", - "migration-stop-failed": "Не вдалось зупинити міграції", - "migration-stopped": "Міграції зупинені успішно", + "migration-pause-failed": "Failed to pause migrations", + "migration-paused": "Migrations paused successfully", + "migration-progress": "Migration Progress", + "migration-start-failed": "Failed to start migrations", + "migration-started": "Migrations started successfully", + "migration-status": "Migration Status", + "migration-stop-confirm": "Are you sure you want to stop all migrations?", + "migration-stop-failed": "Failed to stop migrations", + "migration-stopped": "Migrations stopped successfully", "mongodb-gridfs-storage": "MongoDB GridFS Storage", - "pause-all-migrations": "Призупинити всі міграції", + "pause-all-migrations": "Pause All Migrations", "s3-access-key": "S3 Access Key", "s3-access-key-description": "AWS S3 access key for authentication", "s3-access-key-placeholder": "Enter S3 access key", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Статус", - "migration-progress-details": "Деталі", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "завершено", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Розмір (байти)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Не вдалось створити користувача", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "Користувач вже існує", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Не вдалось створити користувача", - "login": "Login", - "confirm": "Підтвердити", - "error": "Вийти", - "file": "Вийти", - "log": "Log", - "logout": "Вийти", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/uk.i18n.json b/imports/i18n/data/uk.i18n.json index c97b39717..e1d01b105 100644 --- a/imports/i18n/data/uk.i18n.json +++ b/imports/i18n/data/uk.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "видалено коментар %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Шаблони", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Додати вкладення", @@ -98,7 +86,6 @@ "add-card": "Додати картку", "add-card-to-top-of-list": "Додати картку на початок списку", "add-card-to-bottom-of-list": "Додати картку у кінець списку", - "addListPopup-title": "Додати список", "setListWidthPopup-title": "Встановити ширину", "set-list-width": "Встановити ширину", "set-list-width-value": "Встановити мін. та макс. ширину (у пікселях)", @@ -122,10 +109,10 @@ "add-after-list": "Додати після списку", "add-members": "Додати учасників", "added": "Додано", - "addMemberPopup-title": "Додати учасників", + "addMemberPopup-title": "Учасники", "memberPopup-title": "Налаштування учасників", "admin": "Адміністратор", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Може переглядати і редагувати картки, видаляти учасників та змінювати налаштування для дошки.", "admin-announcement": "Оголошення", "admin-announcement-active": "Активне системне оголошення", "admin-announcement-title": "Оголошення адміністратора", @@ -167,16 +154,12 @@ "board-background-image-url": "Посилання на фонове зображення", "add-background-image": "Додати фонове зображення", "remove-background-image": "Видалити фонове зображення", - "show-at-all-boards-page": "Показувати на сторінці всіх дошок", - "board-info-on-my-boards": "Налаштування всіх дошок", - "boardInfoOnMyBoardsPopup-title": "Налаштування всіх дошок", + "show-at-all-boards-page" : "Показувати на сторінці всіх дошок", + "board-info-on-my-boards" : "Налаштування всіх дошок", + "boardInfoOnMyBoardsPopup-title" : "Налаштування всіх дошок", "boardInfoOnMyBoards-title": "Налаштування всіх дошок", "show-card-counter-per-list": "Показувати кількість карток у списку", "show-board_members-avatar": "Показати аватари учасників дошки", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s зірок", "board-not-found": "Дошка не знайдена", "board-private-info": "Ця дошка буде <strong>приватною</strong>.", @@ -197,10 +180,10 @@ "desktop-mode": "Desktop Mode", "mobile-mode": "Mobile Mode", "mobile-desktop-toggle": "Toggle between Mobile and Desktop Mode", - "zoom-in": "Збільшити", - "zoom-out": "Зменшити", - "click-to-change-zoom": "Натисніть щоб змінити масштабування", - "zoom-level": "Масштабування", + "zoom-in": "Zoom In", + "zoom-out": "Zoom Out", + "click-to-change-zoom": "Click to change zoom level", + "zoom-level": "Zoom Level", "enter-zoom-level": "Enter zoom level (50-300%):", "board-view-cal": "Календар", "board-view-swimlanes": "Свімлейни", @@ -208,8 +191,8 @@ "board-view-gantt": "Гантт", "board-view-lists": "Списки", "bucket-example": "Like \"Bucket List\" for example", - "calendar-previous-month-label": "Попередній місяць", - "calendar-next-month-label": "Наступний місяць", + "calendar-previous-month-label": "Previous Month", + "calendar-next-month-label": "Next Month", "cancel": "Скасувати", "card-archived": "Цю картку переміщено до архіву.", "board-archived": "Цю дошку переміщено до архіву.", @@ -286,8 +269,6 @@ "change-permissions": "Змінити права доступу", "change-settings": "Змінити налаштування", "changeAvatarPopup-title": "Змінити аватар", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Змінити мову", "changePasswordPopup-title": "Змінити пароль", "changePermissionsPopup-title": "Змінити права доступу", @@ -335,16 +316,10 @@ "comment-placeholder": "Написати коментар", "comment-only": "Тільки коментарі", "comment-only-desc": "Може коментувати тільки картки.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Ви впевнені, що хочете видалити коментар?", "deleteCommentPopup-title": "Видалити коментар?", "no-comments": "Немає коментарів", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Не може бачити коментарі та активність.", "worker": "Робітник", "worker-desc": "Може тільки переміщати картки, призначати себе до картки і коментувати.", "computer": "Комп'ютер", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Ви впевнені, що хочете видалити контрольний список?", "subtaskDeletePopup-title": "Видалити підзадачу?", "checklistDeletePopup-title": "Видалити контрольний список?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Скопіювати посилання на картку в буфер обміну", "copy-text-to-clipboard": "Скопіювати текст у буфер обміну", "linkCardPopup-title": "Зв'язати картку", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[{\"title\": \"Перший заголовок картки\", \"description\":\"Перший опис картки\"}, {\"title\":\"Другий заголовок картки\",\"description\":\"Другий опис картки\"},{\"title\":\"Останній заголовок картки\",\"description\":\"Останній опис картки\"} ]", "create": "Створити", "createBoardPopup-title": "Створити дошку", - "createTemplateContainerPopup-title": "Додати шаблон контейнера", "chooseBoardSourcePopup-title": "Імпортувати дошку", "createLabelPopup-title": "Створити мітку", "createCustomField": "Створити поле", @@ -383,10 +356,10 @@ "custom-field-text": "Текст", "custom-fields": "Налаштовані поля", "date": "Дата", - "date-format": "Формат дати", - "date-format-yyyy-mm-dd": "РРРР-ММ-ДД", - "date-format-dd-mm-yyyy": "ДД-ММ-РРРР", - "date-format-mm-dd-yyyy": "ММ-ДД-РРРР", + "date-format": "Date Format", + "date-format-yyyy-mm-dd": "YYYY-MM-DD", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Відхилити", "default-avatar": "Аватар за замовчуванням", "delete": "Видалити", @@ -412,7 +385,7 @@ "editNotificationPopup-title": "Редагувати сповіщення", "editProfilePopup-title": "Редагувати профіль", "email": "Email", - "email-address": "Адреса електронної пошти", + "email-address": "Email Address", "email-enrollAccount-subject": "Акаунт створений для вас на __siteName__", "email-enrollAccount-text": "Привіт __user__,\n\nЩоб почати користуватися сервісом, просто натисніть на посилання нижче.\n\n__url__\n\nДякуємо.", "email-fail": "Невдача при відправленні email", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Ви можете перемістити список до архіву, щоб прибрати його з дошки та зберегти активність.", "lists": "Списки", "swimlanes": "Свімлейни", - "calendar": "Календар", - "gantt": "Гантт", "log-out": "Вийти", "log-in": "Увійти", "loginPopup-title": "Увійти", "memberMenuPopup-title": "Налаштування користувачів", - "grey-icons": "Grey Icons", "members": "Користувачі", "menu": "Меню", "move-selection": "Перенести вибране", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Перемістити на низ", "moveCardToTop-title": "Перемістити на початок", "moveSelectionPopup-title": "Перенести вибране", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Мультивибір", "multi-selection-label": "Встановити мітку для вибору", "multi-selection-member": "Встановити учасника для вибору", @@ -587,8 +555,6 @@ "no-results": "Немає результатів", "normal": "Звичайний", "normal-desc": "Може переглядати та редагувати картки. Не може змінювати налаштування.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Запрошення ще не прийнято", "notify-participate": "Отримувати оновлення по будь-яких картках, де ви берете участь як творець або учасник", "notify-watch": "Отримувати оновлення по будь-яких дошках, списках або картках, за якими ви спостерігаєте", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Дозволити зміну Email", "accounts-allowUserNameChange": "Дозволити зміну імені користувача", "tableVisibilityMode-allowPrivateOnly": "Видимість дошок: дозволити тільки приватні дошки", - "tableVisibilityMode": "Видимість дошок", + "tableVisibilityMode" : "Видимість дошок", "createdAt": "Створено", "modifiedAt": "Змінено", "verified": "Перевірено", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Змінити дату отримання", "editCardEndDatePopup-title": "Змінити дату закінчення", "setCardColorPopup-title": "Встановити колір", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Вибрати колір", "setSwimlaneColorPopup-title": "Вибрати колір", "setListColorPopup-title": "Вибрати колір", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Усі списки, картки, мітки та діяльність будуть видалені, і ви не зможете відновити вміст дошки. Немає відкату.", "boardDeletePopup-title": "Видалити дошку?", "delete-board": "Видалити дошку", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Підзадачі для дошки __board__", @@ -942,13 +905,6 @@ "authentication-method": "метод автентифікації", "authentication-type": "тип автентифікації", "custom-product-name": "Назва спеціального продукту", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Макет", "hide-logo": "Сховати логотип", "hide-card-counter-list": "Сховати лічильник карток на Всіх дошках", @@ -979,8 +935,6 @@ "a-endAt": "змінено час закінчення на", "a-startAt": "змінено час початку на", "a-receivedAt": "змінено час отримання на", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "поточний час завершення %s наближається", "pastdue": "поточний час завершення %s пройшов", "duenow": "поточний час завершення %s сьогодні", @@ -989,7 +943,7 @@ "act-almostdue": "нагадувало, що поточний термін (__timeValue__) __card__ наближається", "act-pastdue": "нагадувало, що поточний термін (__timeValue__) __card__ пройшов", "act-duenow": "нагадувало, що поточний термін (__timeValue__) __card__ зараз", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Вас згадали в [__board__] __list__/__card__", "delete-user-confirm-popup": "Ви дійсно бажаєте видалити цей обліковий запис? Цю дію не можна скасувати.", "delete-team-confirm-popup": "Ви впевнені, що хочете видалити цю команду? Немає можливості відмінити.", "delete-org-confirm-popup": "Ви впевнені, що хочете видалити цю організацію? Немає можливості відмінити.", @@ -1013,7 +967,6 @@ "view-all": "Показати все", "filter-by-unread": "Фільтрувати непрочитані", "mark-all-as-read": "Позначити всі як прочитані", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Видалити всі прочитані", "allow-rename": "Дозволити перейменування", "allowRenamePopup-title": "Дозволити перейменування", @@ -1048,10 +1001,6 @@ "person": "Особа", "my-cards": "Мої картки", "card": "Картка", - "today": "Сьогодні", - "day": "День", - "week": "Тиждень", - "month": "Місяць", "list": "Список", "board": "Дошка", "context-separator": "/", @@ -1341,39 +1290,34 @@ "hideAllChecklistItems": "Приховати всі пункти чек-листа", "support": "Підтримка", "supportPopup-title": "Підтримка", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", - "accessibility": "Доступність", + "accessibility": "Accessibility", "accessibility-page-enabled": "Увімкнено сторінку доступності", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", "accessibility-title": "Accessibility title", "accessibility-content": "Доступність контенту", - "accounts-lockout-settings": "Налаштування захисту від брутфорсу", + "accounts-lockout-settings": "Brute Force Protection Settings", "accounts-lockout-info": "These settings control how login attempts are protected against brute force attacks.", "accounts-lockout-known-users": "Settings for known users (correct username, wrong password)", "accounts-lockout-unknown-users": "Settings for unknown users (non-existent username)", - "accounts-lockout-failures-before": "Кількість невдалих спроб", - "accounts-lockout-period": "Період блокування (секунди)", + "accounts-lockout-failures-before": "Failures before lockout", + "accounts-lockout-period": "Lockout period (seconds)", "accounts-lockout-failure-window": "Failure window (seconds)", - "accounts-lockout-settings-updated": "Налаштування захисту від брутфорсу були оновлені", - "accounts-lockout-locked-users": "Заблоковані користувачі", + "accounts-lockout-settings-updated": "Brute force protection settings have been updated", + "accounts-lockout-locked-users": "Locked Users", "accounts-lockout-locked-users-info": "Users currently locked out due to too many failed login attempts", - "accounts-lockout-no-locked-users": "Нема заблокованих користувачів", - "accounts-lockout-failed-attempts": "Невдалих спроб", - "accounts-lockout-remaining-time": "Залишилось часу", - "accounts-lockout-user-unlocked": "Користувач був успішно розблокований", - "accounts-lockout-confirm-unlock": "Ви впевнені, що хочете розблокувати цього користувача?", - "accounts-lockout-confirm-unlock-all": "Ви впевнені, що хочете розблокувати усіх користувачів?", - "accounts-lockout-show-locked-users": "Показати тільки заблокованих користувачів", - "accounts-lockout-user-locked": "Користувач заблокований", - "accounts-lockout-click-to-unlock": "Натисніть, щоб розблокувати користувача", + "accounts-lockout-no-locked-users": "There are currently no locked users", + "accounts-lockout-failed-attempts": "Failed Attempts", + "accounts-lockout-remaining-time": "Remaining Time", + "accounts-lockout-user-unlocked": "User has been unlocked successfully", + "accounts-lockout-confirm-unlock": "Are you sure you want to unlock this user?", + "accounts-lockout-confirm-unlock-all": "Are you sure you want to unlock all locked users?", + "accounts-lockout-show-locked-users": "Show locked users only", + "accounts-lockout-user-locked": "User is locked", + "accounts-lockout-click-to-unlock": "Click to unlock this user", "accounts-lockout-status": "Статус", "admin-people-filter-show": "Show:", "admin-people-filter-all": "Усі Користувачі", - "admin-people-filter-locked": "Тільки заблоковані користувачі", + "admin-people-filter-locked": "Locked Users Only", "admin-people-filter-active": "Активно", "admin-people-filter-inactive": "Not Active", "admin-people-active-status": "Active Status", @@ -1403,50 +1347,20 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Час", - "cron-error-message": "Error Message", - "cron-error-details": "Деталі", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "Всі міграції", - "select-migration": "Оберіть міграцію", - "start": "Початок", - "pause": "Пауза", - "stop": "Стоп", - "migration-starting": "Початок міграцій...", - "migration-pausing": "Призупинення міграцій...", - "migration-stopping": "Зупинка міграцій...", - "migration-pause-failed": "Не вдалось призупинити міграції", - "migration-paused": "Міграції успішно призупинено", - "migration-progress": "Прогрес міграцій", - "migration-start-failed": "Не вдалось почати міграції", - "migration-started": "Міграції розпочалися успішно", - "migration-not-needed": "Міграції не потрібні", - "migration-status": "Статус міграцій", - "migration-stop-confirm": "Ви впевнені, що хочете зупинити всі міграції?", - "migration-stop-failed": "Не вдалось зупинити міграції", - "migration-stopped": "Міграції зупинені успішно", + "migration-pause-failed": "Failed to pause migrations", + "migration-paused": "Migrations paused successfully", + "migration-progress": "Migration Progress", + "migration-start-failed": "Failed to start migrations", + "migration-started": "Migrations started successfully", + "migration-status": "Migration Status", + "migration-stop-confirm": "Are you sure you want to stop all migrations?", + "migration-stop-failed": "Failed to stop migrations", + "migration-stopped": "Migrations stopped successfully", "mongodb-gridfs-storage": "MongoDB GridFS Storage", - "pause-all-migrations": "Призупинити всі міграції", + "pause-all-migrations": "Pause All Migrations", "s3-access-key": "S3 Access Key", "s3-access-key-description": "AWS S3 access key for authentication", "s3-access-key-placeholder": "Enter S3 access key", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Статус", - "migration-progress-details": "Деталі", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "завершено", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Розмір (байти)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Паролі не співпадають", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "Користувач вже існує", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Не вдалось створити користувача", - "login": "Login", - "confirm": "Підтвердити", - "error": "Помилка", - "file": "Файл", - "log": "Log", - "logout": "Вийти", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/uz-AR.i18n.json b/imports/i18n/data/uz-AR.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/uz-AR.i18n.json +++ b/imports/i18n/data/uz-AR.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/uz-LA.i18n.json b/imports/i18n/data/uz-LA.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/uz-LA.i18n.json +++ b/imports/i18n/data/uz-LA.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/uz-UZ.i18n.json b/imports/i18n/data/uz-UZ.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/uz-UZ.i18n.json +++ b/imports/i18n/data/uz-UZ.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/uz.i18n.json b/imports/i18n/data/uz.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/uz.i18n.json +++ b/imports/i18n/data/uz.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ve-CC.i18n.json b/imports/i18n/data/ve-CC.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/ve-CC.i18n.json +++ b/imports/i18n/data/ve-CC.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ve-PP.i18n.json b/imports/i18n/data/ve-PP.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/ve-PP.i18n.json +++ b/imports/i18n/data/ve-PP.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/ve.i18n.json b/imports/i18n/data/ve.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/ve.i18n.json +++ b/imports/i18n/data/ve.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/vi-VN.i18n.json b/imports/i18n/data/vi-VN.i18n.json index eccce8633..9dbb74fa4 100644 --- a/imports/i18n/data/vi-VN.i18n.json +++ b/imports/i18n/data/vi-VN.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/vi.i18n.json b/imports/i18n/data/vi.i18n.json index c972710d9..8843c5833 100644 --- a/imports/i18n/data/vi.i18n.json +++ b/imports/i18n/data/vi.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "đã xoá lời bình %s", "activity-receivedDate": "đã sửa ngày nhận đến %s của %s", "activity-startDate": "đã sửa ngày bắt đầu thành %s của %s", - "allboards.starred": "Starred", - "allboards.templates": "Các mẫu", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "đã sửa ngày hết hạn thành %s của %s", "activity-endDate": "đã sửa ngày kết thúc thành %s của %s", "add-attachment": "Thêm Bản Đính Kèm", @@ -98,7 +86,6 @@ "add-card": "Thêm Thẻ", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Thêm Thành Viên", "added": "Đã Thêm", - "addMemberPopup-title": "Thêm Thành Viên", + "addMemberPopup-title": "Thành Viên", "memberPopup-title": "Cài đặt thành viên", "admin": "Quản Trị Viên", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Có thể xem và chỉnh sửa những thẻ, xóa thành viên và thay đổi cài đặt cho bảng.", "admin-announcement": "Thông báo", "admin-announcement-active": "Thông báo trên toàn hệ thống đang hoạt động", "admin-announcement-title": "Thông báo từ Quản trị viên", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s sao", "board-not-found": "Không tìm được bảng", "board-private-info": "Bảng này sẽ chuyển sang chế độ <strong>riêng tư</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Thay đổi quyền", "change-settings": "Thay đổi Cài đặt", "changeAvatarPopup-title": "Thay đổi hình đại diện", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Thay đổi ngôn ngữ", "changePasswordPopup-title": "Đổi mật khẩu", "changePermissionsPopup-title": "Thay đổi quyền", @@ -335,16 +316,10 @@ "comment-placeholder": "Viết Bình Luận", "comment-only": "Chỉ bình luận", "comment-only-desc": "Chỉ có thể nhận xét về thẻ.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "Không có bình luận", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Không thể xem bình luận và hoạt động.", "worker": "Worker", "worker-desc": "Chỉ có thể di chuyển thẻ, tự gán thẻ và nhận xét.", "computer": "Máy tính", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Sao chép liên kết thẻ vào khay nhớ tạm", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Thẻ liên kết", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"Tiêu đề thẻ đầu tiên\", \"description\":\"Mô tả thẻ đầu tiên\"}, {\"title\":\"Tiêu đề thẻ thứ hai\",\"description\":\"Mô tả thẻ thứ hai\"},{\"title\":\"Tiêu đề thẻ cuối cùng\",\"description\":\"Mô tả thẻ cuối cùng\"} ]", "create": "Tạo", "createBoardPopup-title": "Tạo Bảng", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Nhập bảng", "createLabelPopup-title": "Tạo nhãn", "createCustomField": "Tạo Trường", @@ -385,7 +358,7 @@ "date": "Ngày", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Từ chối", "default-avatar": "Hình đại diện mặc định", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "Bạn có thể di chuyển danh sách vào Lưu trữ để xóa danh sách đó khỏi Bảng và duy trì hoạt động.", "lists": "Danh sách", "swimlanes": "Làn ngang", - "calendar": "Lịch", - "gantt": "Biểu đồ Gantt", "log-out": "Đăng Xuất", "log-in": "Đăng nhập", "loginPopup-title": "Đăng nhập", "memberMenuPopup-title": "Cài đặt thành viên", - "grey-icons": "Grey Icons", "members": "Thành Viên", "menu": "Menu", "move-selection": "Di chuyển lựa chọn", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Di chuyển xuống dưới cùng", "moveCardToTop-title": "Di chuyển lên đầu", "moveSelectionPopup-title": "Di chuyển lựa chọn", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Chọn nhiều", "multi-selection-label": "Đặt nhãn để lựa chọn", "multi-selection-member": "Đặt thành viên để lựa chọn", @@ -587,8 +555,6 @@ "no-results": "Không có kết quả", "normal": "Bình thường", "normal-desc": "Có thể xem và chỉnh sửa thẻ. Không thể thay đổi cài đặt.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Lời mời chưa được chấp nhận", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Nhận thông tin cập nhật cho bất kỳ bảng, danh sách hoặc thẻ nào bạn đang xem", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Cho phép thay đổi email", "accounts-allowUserNameChange": "Cho phép thay đổi tên người dùng", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Được tạo lúc", "modifiedAt": "Được sửa đổi lúc", "verified": "Đã xác minh", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Thay đổi ngày nhận", "editCardEndDatePopup-title": "Thay đổi ngày kết thúc", "setCardColorPopup-title": "Đặt màu", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Chọn một màu", "setSwimlaneColorPopup-title": "Chọn một màu", "setListColorPopup-title": "Chọn một màu", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "Tất cả danh sách, thẻ, nhãn và hoạt động sẽ bị xóa và bạn sẽ không thể khôi phục nội dung bảng. Không thể hoàn tác.", "boardDeletePopup-title": "Xoá Bảng?", "delete-board": "Xoá Bảng", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Nhiệm vụ phụ cho __board__ bảng", @@ -942,13 +905,6 @@ "authentication-method": "Phương thức xác thực", "authentication-type": "Loại xác thực", "custom-product-name": "Tên sản phẩm tùy chỉnh", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Bố cục", "hide-logo": "Ẩn Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "đã sửa đổi thời gian kết thúc thành", "a-startAt": "thời gian bắt đầu được sửa đổi thành", "a-receivedAt": "đã sửa đổi thời gian nhận được", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "thời gian đến hạn hiện tại %s đang đến gần", "pastdue": "thời gian đến hạn hiện tại %s đã qua", "duenow": "giờ đến hạn hiện tại %s là hôm nay", @@ -989,7 +943,7 @@ "act-almostdue": "đang nhắc nhở thời hạn hiện tại là (__timeValue__) của __card__ đang đến gần", "act-pastdue": "đang nhắc nhở thời hạn hiện tại (__timeValue__) của __card__ đã qua", "act-duenow": "đã được nhắc nhở hiện tại đến hạn (__timeValue__) của __card__ bây giờ là", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "Bạn đã được đề cập trong [__board__] __list__/__card__", "delete-user-confirm-popup": "Bạn có chắc chắn muốn xóa tài khoản này không? Không thể hoàn tác.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "Xem Tất cả", "filter-by-unread": "Lọc theo Chưa đọc", "mark-all-as-read": "Đánh dấu tất cả là đã đọc", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Xóa tất cả đã đọc", "allow-rename": "Cho phép đổi tên", "allowRenamePopup-title": "Cho phép đổi tên", @@ -1048,10 +1001,6 @@ "person": "Cá nhân", "my-cards": "Thẻ của tôi", "card": "Thẻ", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "Danh sách", "board": "Bảng", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Thời gian", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Bắt đầu", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Trạng thái", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Đã hoàn thành", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/vl-SS.i18n.json b/imports/i18n/data/vl-SS.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/vl-SS.i18n.json +++ b/imports/i18n/data/vl-SS.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/vo.i18n.json b/imports/i18n/data/vo.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/vo.i18n.json +++ b/imports/i18n/data/vo.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/wa-RR.i18n.json b/imports/i18n/data/wa-RR.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/wa-RR.i18n.json +++ b/imports/i18n/data/wa-RR.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/wa.i18n.json b/imports/i18n/data/wa.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/wa.i18n.json +++ b/imports/i18n/data/wa.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/wo.i18n.json b/imports/i18n/data/wo.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/wo.i18n.json +++ b/imports/i18n/data/wo.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/wuu-Hans.i18n.json b/imports/i18n/data/wuu-Hans.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/wuu-Hans.i18n.json +++ b/imports/i18n/data/wuu-Hans.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/xh.i18n.json b/imports/i18n/data/xh.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/xh.i18n.json +++ b/imports/i18n/data/xh.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/yi.i18n.json b/imports/i18n/data/yi.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/yi.i18n.json +++ b/imports/i18n/data/yi.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/yo.i18n.json b/imports/i18n/data/yo.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/yo.i18n.json +++ b/imports/i18n/data/yo.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/yue_CN.i18n.json b/imports/i18n/data/yue_CN.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/yue_CN.i18n.json +++ b/imports/i18n/data/yue_CN.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/zgh.i18n.json b/imports/i18n/data/zgh.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/zgh.i18n.json +++ b/imports/i18n/data/zgh.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/zh-CN.i18n.json b/imports/i18n/data/zh-CN.i18n.json index 1f514d431..2f3dba633 100644 --- a/imports/i18n/data/zh-CN.i18n.json +++ b/imports/i18n/data/zh-CN.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "评论已删除", "activity-receivedDate": "已将接收日期从 %s 修改为 %s", "activity-startDate": "已将开始日期从 %s 修改为 %s", - "allboards.starred": "已星标", - "allboards.templates": "模板", - "allboards.remaining": "Remaining", - "allboards.workspaces": "工作区", - "allboards.add-workspace": "增加工作区", - "allboards.add-workspace-prompt": "工作区名称", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "编辑工作区", - "allboards.edit-workspace-name": "工作区名称", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "已将到期日期从 %s 修改为 %s", "activity-endDate": "已将结束日期从 %s 修改为 %s", "add-attachment": "添加附件", @@ -98,7 +86,6 @@ "add-card": "添加卡片", "add-card-to-top-of-list": "添加卡片到列表顶部", "add-card-to-bottom-of-list": "添加卡片到列表底部", - "addListPopup-title": "添加列表", "setListWidthPopup-title": "设定宽度", "set-list-width": "设定宽度", "set-list-width-value": "设定最小与最大宽度(像素)", @@ -122,10 +109,10 @@ "add-after-list": "新增到列表", "add-members": "添加成员", "added": "添加", - "addMemberPopup-title": "添加成员", + "addMemberPopup-title": "成员", "memberPopup-title": "成员设置", "admin": "管理员", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "可以浏览并编辑卡片,移除成员,并且更改该看板的设置", "admin-announcement": "通知", "admin-announcement-active": "激活系统通知", "admin-announcement-title": "管理员的通知", @@ -167,16 +154,12 @@ "board-background-image-url": "背景图片网址", "add-background-image": "新增背景图片", "remove-background-image": "删除背景图片", - "show-at-all-boards-page": "现在在所有面板页面", - "board-info-on-my-boards": "面板全局设置", - "boardInfoOnMyBoardsPopup-title": "面板全局设置", + "show-at-all-boards-page" : "现在在所有面板页面", + "board-info-on-my-boards" : "面板全局设置", + "boardInfoOnMyBoardsPopup-title" : "面板全局设置", "boardInfoOnMyBoards-title": "面板全局设置", "show-card-counter-per-list": "显示每个列表的卡数", "show-board_members-avatar": "显示面板成员头像", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s 星标", "board-not-found": "看板不存在", "board-private-info": "该看板将被设为 <strong>私有</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "更改权限", "change-settings": "更改设置", "changeAvatarPopup-title": "更改头像", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "更改语言", "changePasswordPopup-title": "更改密码", "changePermissionsPopup-title": "更改权限", @@ -335,16 +316,10 @@ "comment-placeholder": "添加评论", "comment-only": "仅能评论", "comment-only-desc": "仅能在卡片上评论。", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "确定要删除评论?", "deleteCommentPopup-title": "删除评论?", "no-comments": "暂无评论", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "无法查看评论和活动。", "worker": "人员", "worker-desc": "只能移动卡片,分配给卡片和评论", "computer": "从本机上传", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "确定要删除清单吗?", "subtaskDeletePopup-title": "删除子任务?", "checklistDeletePopup-title": "删除待办清单?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "复制卡片链接到剪贴板", "copy-text-to-clipboard": "复制文本到剪贴板", "linkCardPopup-title": "链接卡片", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"第一个卡片的标题\", \"description\":\"第一个卡片的描述\"}, {\"title\":\"第二个卡片的标题\",\"description\":\"第二个卡片的描述\"},{\"title\":\"最后一个卡片的标题\",\"description\":\"最后一个卡片的描述\"} ]", "create": "创建", "createBoardPopup-title": "创建看板", - "createTemplateContainerPopup-title": "新增模板容器", "chooseBoardSourcePopup-title": "导入看板", "createLabelPopup-title": "创建标签", "createCustomField": "创建字段", @@ -385,7 +358,7 @@ "date": "日期", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "拒绝", "default-avatar": "默认头像", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "您可以移动列表到归档以将其从看板中移除并保留活动。", "lists": "列表", "swimlanes": "泳道图", - "calendar": "日历", - "gantt": "甘特图", "log-out": "退出", "log-in": "登录", "loginPopup-title": "登录", "memberMenuPopup-title": "成员设置", - "grey-icons": "Grey Icons", "members": "成员", "menu": "菜单", "move-selection": "移动选择", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "移动至底端", "moveCardToTop-title": "移动至顶端", "moveSelectionPopup-title": "移动选择", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "多选", "multi-selection-label": "设置标签", "multi-selection-member": "选择成员", @@ -587,8 +555,6 @@ "no-results": "无结果", "normal": "普通", "normal-desc": "可以创建以及编辑卡片,无法更改设置。", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "邀请尚未接受", "notify-participate": "接收任何卡的更新作为创建者或成员", "notify-watch": "接收所有关注的面板、列表、及卡片的更新", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "允许邮箱变更", "accounts-allowUserNameChange": "允许变更用户名", "tableVisibilityMode-allowPrivateOnly": "看板可见性:仅显示私人看板", - "tableVisibilityMode": "看板可见性", + "tableVisibilityMode" : "看板可见性", "createdAt": "创建于", "modifiedAt": "修改时间", "verified": "已验证", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "修改接收日期", "editCardEndDatePopup-title": "修改结束日期", "setCardColorPopup-title": "设置颜色", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "选择一种颜色", "setSwimlaneColorPopup-title": "选择一种颜色", "setListColorPopup-title": "选择一种颜色", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "所有列表、卡片、标签和活动都回被删除,将无法恢复看板内容。不支持撤销。", "boardDeletePopup-title": "删除看板?", "delete-board": "删除看板", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "__board__ 看板的子任务", @@ -942,13 +905,6 @@ "authentication-method": "认证方式", "authentication-type": "认证类型", "custom-product-name": "自定义产品名称", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "布局", "hide-logo": "隐藏图标", "hide-card-counter-list": "隐藏所有卡片计数器列表", @@ -979,8 +935,6 @@ "a-endAt": "修改结束时间", "a-startAt": "修改开始时间", "a-receivedAt": "修改接收时间", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "当前到期时间%s即将到来", "pastdue": "当前到期时间%s已过", "duenow": "当前到期时间%s为今天", @@ -989,7 +943,7 @@ "act-almostdue": "__card__ 的当前到期提醒(__timeValue__) 正在接近", "act-pastdue": "__card__ 的当前到期提醒(__timeValue__) 已经过去了", "act-duenow": "__card__ 的当前到期提醒(__timeValue__) 现在到期", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "[__board__] __list__/__card__ 提到了您", "delete-user-confirm-popup": "确实要删除此帐户吗?此操作无法撤销。", "delete-team-confirm-popup": "请确认是否删除此团队?此操作无法撤销。", "delete-org-confirm-popup": "请确认是否删除此组织?此操作无法撤销。", @@ -1013,7 +967,6 @@ "view-all": "查看全部", "filter-by-unread": "过滤未读", "mark-all-as-read": "标记全部已读", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "移除所有已读", "allow-rename": "允许重命名", "allowRenamePopup-title": "允许重命名", @@ -1048,10 +1001,6 @@ "person": "人员", "my-cards": "我的卡片", "card": "卡片", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "列表", "board": "看板", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "隐藏所有待办清单项目", "support": "支持", "supportPopup-title": "支持", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "已启用无障碍页面", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "时间", - "cron-error-message": "Error Message", - "cron-error-details": "详情", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "完成", - "idle": "空闲", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "开始", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "完成", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "状态", - "migration-progress-details": "详情", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "已完成", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "权重", - "cron": "定时任务", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "仓库", - "repository-name": "仓库名称", - "size-bytes": "大小(字节)", - "last-modified": "上次修改", - "no-repositories": "No repositories found", - "create-repository": "创建仓库", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "用户名和密码必填", - "password-mismatch": "密码不符", - "username-too-short": "用户名必须不少于3个字符", - "user-exists": "用户账号已存在", - "account-created": "账号已创建!您现在可以登入了。", - "account-creation-failed": "账号创建错误", - "login": "登录", - "confirm": "确认", - "error": "错误", - "file": "文件", - "log": "日志", - "logout": "注销", - "server": "服务器", - "protocol": "协议" + "idle": "空闲", + "complete": "完成", + "cron": "定时任务" } diff --git a/imports/i18n/data/zh-GB.i18n.json b/imports/i18n/data/zh-GB.i18n.json index 095c50e73..151bcf1fd 100644 --- a/imports/i18n/data/zh-GB.i18n.json +++ b/imports/i18n/data/zh-GB.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/zh-HK.i18n.json b/imports/i18n/data/zh-HK.i18n.json index f163c46cf..5c71d764d 100644 --- a/imports/i18n/data/zh-HK.i18n.json +++ b/imports/i18n/data/zh-HK.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "登入", "loginPopup-title": "登入", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/zh-Hans.i18n.json b/imports/i18n/data/zh-Hans.i18n.json index 067c97746..1938e0590 100644 --- a/imports/i18n/data/zh-Hans.i18n.json +++ b/imports/i18n/data/zh-Hans.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "创建", "createBoardPopup-title": "创建看板", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "导入看板", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/zh-Hant.i18n.json b/imports/i18n/data/zh-Hant.i18n.json index 28bbf0394..c1d5ed8b7 100644 --- a/imports/i18n/data/zh-Hant.i18n.json +++ b/imports/i18n/data/zh-Hant.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/zh-TW.i18n.json b/imports/i18n/data/zh-TW.i18n.json index 8915b3ebf..a6f66caed 100644 --- a/imports/i18n/data/zh-TW.i18n.json +++ b/imports/i18n/data/zh-TW.i18n.json @@ -61,35 +61,23 @@ "activity-sent": "已寄送 %s 到 %s", "activity-unjoined": "已解除關聯 %s", "activity-subtask-added": "已新增子任務到 %s", - "activity-checked-item": "勾選 %s 於清單 %s 共 %s", - "activity-unchecked-item": "取消勾選 %s 於清單 %s 共 %s", + "activity-checked-item": "勾選%s於清單%s 共 %s", + "activity-unchecked-item": "未勾選 %s 於清單 %s 共 %s", "activity-checklist-added": "已新增待辦清單 %s", "activity-checklist-removed": "已刪除%s的待辦清單", "activity-checklist-completed": "已完成待辦清單 %s 共 %s 項", "activity-checklist-uncompleted": "未完成待辦清單 %s 共 %s 項", "activity-checklist-item-added": "新增待辦清單項目從 %s 到 %s", - "activity-checklist-item-removed": "已從「%s」在 %s 中移除檢查清單項目", + "activity-checklist-item-removed": "已從 '%s' 於 %s中 移除一個清單項", "add": "新增", "activity-checked-item-card": "勾選 %s 與清單 %s 中", "activity-unchecked-item-card": "取消勾選 %s 於清單 %s中", "activity-checklist-completed-card": "完成檢查清單__checklist__ 卡片 __card__ 清單 __list__ 泳道 __swimlane__ 看板 __board__", - "activity-checklist-uncompleted-card": "未完成檢查清單 %s", + "activity-checklist-uncompleted-card": "未完成清單 %s", "activity-editComment": "評論已編輯", "activity-deleteComment": "評論已刪除", "activity-receivedDate": "已編輯收到日期為 %s %s", "activity-startDate": "已編輯起始日期為 %s %s", - "allboards.starred": "已加星號", - "allboards.templates": "範本", - "allboards.remaining": "剩餘", - "allboards.workspaces": "工作空間", - "allboards.add-workspace": "新增工作空間", - "allboards.add-workspace-prompt": "工作空間名稱", - "allboards.add-subworkspace": "新增子工作空間", - "allboards.add-subworkspace-prompt": "子工作空間名稱", - "allboards.edit-workspace": "編輯工作空間", - "allboards.edit-workspace-name": "工作空間名稱", - "allboards.edit-workspace-icon": "工作空間圖示 (markdown)", - "multi-selection-active": "點選核取方塊以選取看板", "activity-dueDate": "已編輯截止日期為 %s %s", "activity-endDate": "已編輯結束日期為 %s %s", "add-attachment": "新增附件", @@ -98,7 +86,6 @@ "add-card": "新增卡片", "add-card-to-top-of-list": "新增卡片至清單頂部", "add-card-to-bottom-of-list": "新增卡片至清單底部", - "addListPopup-title": "新增清單", "setListWidthPopup-title": "設定寬度", "set-list-width": "設定寬度", "set-list-width-value": "設定最小與最大寬度(畫素)", @@ -116,16 +103,16 @@ "close-add-checklist-item": "關閉向檢查清單表單新增項目", "close-edit-checklist-item": "關閉編輯檢查清單表單的項目", "convertChecklistItemToCardPopup-title": "轉換為卡片", - "add-cover": "新增封面圖片至迷你卡片", + "add-cover": "新增封面圖片至小卡片", "add-label": "新增標籤", "add-list": "新增清單", "add-after-list": "在清單後新增", "add-members": "新增成員", "added": "已新增", - "addMemberPopup-title": "新增成員", + "addMemberPopup-title": "成員", "memberPopup-title": "成員更改", "admin": "管理員", - "admin-desc": "可以檢視與編輯卡片、移除成員、變更看板設定。可以檢視活動紀錄。", + "admin-desc": "可以瀏覽並編輯卡片,移除成員,並且更改該看板的設定", "admin-announcement": "通知", "admin-announcement-active": "啟用系統公告", "admin-announcement-title": "來自管理員的公告訊息", @@ -167,16 +154,12 @@ "board-background-image-url": "背景圖片 URL", "add-background-image": "新增背景圖片", "remove-background-image": "移除背景圖片", - "show-at-all-boards-page": "在所有看板頁面顯示", - "board-info-on-my-boards": "所有看板設定", - "boardInfoOnMyBoardsPopup-title": "所有看板設定", + "show-at-all-boards-page" : "在所有看板頁面顯示", + "board-info-on-my-boards" : "所有看板設定", + "boardInfoOnMyBoardsPopup-title" : "所有看板設定", "boardInfoOnMyBoards-title": "所有看板設定", "show-card-counter-per-list": "顯示每個清單的卡片數", "show-board_members-avatar": "顯示看板成員大頭照", - "board_members": "所有看板成員", - "card_members": "此看板中目前卡片的所有成員", - "board_assignees": "此看板所有卡片的所有承辦人", - "card_assignees": "此看板中目前卡片的所有承辦人", "board-nb-stars": "%s 星星", "board-not-found": "看板不存在", "board-private-info": "此看板將被設為<strong>私有</strong>。", @@ -216,8 +199,8 @@ "card-comments-title": "該卡片有 %s 條評論", "card-delete-notice": "永久刪除是無法復原的,你將會失去這張卡片的所有相關操作記錄。", "card-delete-pop": "所有的活動將從活動摘要中被移除且您將無法重新打開該卡片。此操作無法撤銷。", - "card-delete-suggest-archive": "您可以移動卡片到活動以便從看板中刪除並保留活動。", - "card-archive-pop": "封存卡片後,將不會在此清單看到卡片。", + "card-delete-suggest-archive": "您可以移動卡片到活動以便從看板中刪除並保持活動。", + "card-archive-pop": "封存卡片後,在此清單將不會看的到卡片。", "card-archive-suggest-cancel": "你可以稍後從封存中還原卡片。", "card-due": "到期日", "card-due-on": "期限", @@ -265,7 +248,7 @@ "poker-replay": "重播", "set-estimation": "設定預估時間", "deletePokerPopup-title": "刪除規劃撲克?", - "poker-delete-pop": "刪除操作不可逆。您會失去所有與此規劃撲克相關的所有操作紀錄。", + "poker-delete-pop": "刪除是永遠的,你會失去所有與此規劃撲克相關動作關聯", "cardDeletePopup-title": "刪除卡片?", "cardArchivePopup-title": "封存卡片嗎?", "cardDetailsActionsPopup-title": "卡片操作", @@ -284,11 +267,9 @@ "change-avatar": "更換大頭照", "change-password": "變更密碼", "change-permissions": "變更權限", - "change-settings": "變更設定", - "changeAvatarPopup-title": "變更大頭照", - "delete-avatar-confirm": "您確定您想要刪除此大頭照嗎?", - "deleteAvatarPopup-title": "刪除大頭照?", - "changeLanguagePopup-title": "變更語言", + "change-settings": "更改設定", + "changeAvatarPopup-title": "更換大頭照", + "changeLanguagePopup-title": "更改語系", "changePasswordPopup-title": "變更密碼", "changePermissionsPopup-title": "變更權限", "changeSettingsPopup-title": "更改設定", @@ -329,30 +310,23 @@ "color-slateblue": "青藍", "color-white": "白色", "color-yellow": "黃色", - "unset-color": "取消設定", + "unset-color": "未設定", "comments": "評論", "comment": "評論", - "comment-placeholder": "撰寫評論", + "comment-placeholder": "撰寫文字", "comment-only": "僅能評論", "comment-only-desc": "只能在卡片上發表評論。", - "comment-assigned-only": "僅能評論(限指定卡片)", - "comment-assigned-only-desc": "僅顯示已指派的卡片。僅能評論。", "comment-delete": "確定要刪除此評論?", "deleteCommentPopup-title": "刪除評論", - "no-comments": "無法評論", - "no-comments-desc": "無法檢視評論。", - "read-only": "唯讀", - "read-only-desc": "僅能檢視卡片。無法編輯。", - "read-assigned-only": "唯讀(限指定卡片)", - "read-assigned-only-desc": "僅顯示已指派的卡片。無法編輯。", - "worker": "員工", + "no-comments": "暫無評論", + "no-comments-desc": "無法檢視評論與活動。", + "worker": "工作者", "worker-desc": "只能移動卡片,分配給自己及發表評論。", "computer": "從本機上傳", "confirm-subtask-delete-popup": "確定要刪除子任務嗎?", "confirm-checklist-delete-popup": "確定要刪除檢查清單嗎?", "subtaskDeletePopup-title": "刪除子任務?", "checklistDeletePopup-title": "刪除檢查清單", - "checklistItemDeletePopup-title": "刪除檢查清單項目?", "copy-card-link-to-clipboard": "將卡片連結複製到剪貼簿", "copy-text-to-clipboard": "複製文字到剪貼簿", "linkCardPopup-title": "連結卡片", @@ -363,14 +337,13 @@ "copyManyCardsPopup-format": "[ {\"title\": \"第一個卡片標題\", \"description\":\"第一個卡片描述\"}, {\"title\":\"第二個卡片標題\",\"description\":\"第二個卡片描述\"},{\"title\":\"最後一個卡片標題\",\"description\":\"最後一個卡片描述\"} ]", "create": "建立", "createBoardPopup-title": "建立看板", - "createTemplateContainerPopup-title": "新增範本容器", "chooseBoardSourcePopup-title": "匯入看板", "createLabelPopup-title": "新增標籤", "createCustomField": "建立欄位", "createCustomFieldPopup-title": "建立欄位", "current": "目前", - "custom-field-delete-pop": "此操作將會從所有卡片中移除自訂欄位以及銷毀歷史紀錄,並且無法撤銷。", - "custom-field-checkbox": "核取方塊", + "custom-field-delete-pop": "此操作將會從所有卡片中移除自訂欄位以及銷毀歷史紀錄,並且無法撤消。", + "custom-field-checkbox": "複選框", "custom-field-currency": "貨幣", "custom-field-currency-option": "貨幣代碼", "custom-field-date": "日期", @@ -385,7 +358,7 @@ "date": "日期", "date-format": "日期格式", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "拒絕", "default-avatar": "預設大頭照", @@ -480,8 +453,8 @@ "filter-no-label": "沒有標籤", "filter-member-label": "按成員篩選", "filter-no-member": "沒有成員", - "filter-assignee-label": "按承辦人篩選", - "filter-no-assignee": "沒有承辦人", + "filter-assignee-label": "按代理人篩選", + "filter-no-assignee": "沒有代理人", "filter-custom-fields-label": "按自訂欄位篩選", "filter-no-custom-fields": "沒有自訂欄位", "filter-show-archive": "顯示封存的清單", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "您可以移動清單到封存,以將其從看板中移除並保留活動。", "lists": "清單", "swimlanes": "泳道", - "calendar": "日曆", - "gantt": "甘特圖", "log-out": "登出", "log-in": "登入", "loginPopup-title": "登入", "memberMenuPopup-title": "成員更改", - "grey-icons": "灰色圖示", "members": "成員", "menu": "選單", "move-selection": "移動選取的項目", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "移至最下面", "moveCardToTop-title": "移至最上面", "moveSelectionPopup-title": "移動選取的項目", - "copySelectionPopup-title": "複製選取的項目", - "selection-color": "選取項目顏色", "multi-selection": "多選", "multi-selection-label": "設定標籤到選擇項目", "multi-selection-member": "設定成員到選擇項目", @@ -587,8 +555,6 @@ "no-results": "無結果", "normal": "普通", "normal-desc": "可以建立以及編輯卡片,無法更改。", - "normal-assigned-only": "僅限指定人員普通", - "normal-assigned-only-desc": "僅顯示已指派的卡片。以普通使用者身份編輯", "not-accepted-yet": "邀請尚未接受", "notify-participate": "接收您作為建立者或成員的任何卡片的更新", "notify-watch": "接收您關注的看板、清單或卡片的更新", @@ -636,16 +602,16 @@ "shortcut-autocomplete-members": "自動補齊成員", "shortcut-clear-filters": "清空全部過濾條件", "shortcut-close-dialog": "關閉對話方塊", - "shortcut-filter-my-cards": "篩選我的卡片", - "shortcut-filter-my-assigned-cards": "篩選分配給我的卡片", + "shortcut-filter-my-cards": "過濾我的卡片", + "shortcut-filter-my-assigned-cards": "過濾分配給我的卡片", "shortcut-show-shortcuts": "顯示此快速鍵清單", - "shortcut-toggle-filterbar": "切換篩選條件側邊欄", + "shortcut-toggle-filterbar": "切換過濾程式邊欄", "shortcut-toggle-searchbar": "切換搜索欄", - "shortcut-toggle-sidebar": "切換看板側邊欄", + "shortcut-toggle-sidebar": "切換面板邊欄", "show-cards-minimum-count": "顯示卡片數量,當清單包含多於……", "sidebar-open": "開啟側邊欄", "sidebar-close": "關閉側邊欄", - "signupPopup-title": "建立帳號", + "signupPopup-title": "建立帳戶", "star-board-title": "點擊這裡可將看板加入我的最愛,它將會出現在您的看板列表上方。", "starred-boards": "我的最愛看板", "starred-boards-description": "加入我的最愛的看板將會出現在您的看板列表上方。", @@ -660,7 +626,7 @@ "has-spenttime-cards": "耗時卡", "time": "時間", "title": "標題", - "toggle-assignees": "切換卡片的承辦人 1-9(按加入看板的順序)。", + "toggle-assignees": "切換卡片的代理人 1-9(按加入看板的順序)。", "toggle-labels": "切換卡片的標籤 1-9。多重選擇新增標籤 1-9", "remove-labels-multiselect": "多重選擇移除標籤 1-9", "tracking": "訂閱相關通知", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "允許變更電子郵件", "accounts-allowUserNameChange": "允許修改使用者名稱", "tableVisibilityMode-allowPrivateOnly": "看板可見性:只允許私人看板", - "tableVisibilityMode": "看板可見性", + "tableVisibilityMode" : "看板可見性", "createdAt": "新增於", "modifiedAt": "編輯於", "verified": "已驗證", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "更改接收日期", "editCardEndDatePopup-title": "更改完成日期", "setCardColorPopup-title": "設定顏色", - "setSelectionColorPopup-title": "設定選取項目顏色", "setCardActionsColorPopup-title": "選擇顏色", "setSwimlaneColorPopup-title": "選擇顏色", "setListColorPopup-title": "選擇顏色", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "所有清單、卡片、標籤和活動都會被刪除,將無法恢覆看板內容。不支援撤銷。", "boardDeletePopup-title": "刪除看板?", "delete-board": "刪除看板", - "delete-all-notifications": "刪除所有通知", - "delete-all-notifications-confirm": "您確定要刪除所有通知?此動作無法還原。", "delete-duplicate-lists": "刪除重複的清單", "delete-duplicate-lists-confirm": "您確定嗎?這將會刪除所有相同名稱但不包含卡片的重複清單。", "default-subtasks-board": "__board__ 看板的子任務", @@ -871,7 +834,7 @@ "r-bottom-of": "的尾部", "r-its-list": "其清單", "r-archive": "封存", - "r-unarchive": "從封存中還原", + "r-unarchive": "從封存中恢復", "r-card": "卡片", "r-add": "新增", "r-remove": "移除", @@ -942,13 +905,6 @@ "authentication-method": "認證方式", "authentication-type": "認證類型", "custom-product-name": "自訂產品名稱", - "custom-head-tags-enabled": "啟用自訂 head 標籤", - "custom-head-meta-tags": "自訂 meta 標籤 (HTML)", - "custom-head-link-tags": "自訂連結標籤 (HTML)", - "custom-manifest-enabled": "啟用自訂網路清單", - "custom-head-manifest-content": "自訂網路清單內容 (JSON)", - "custom-assetlinks-enabled": "啟用自訂 assetlinks.json", - "custom-assetlinks-content": "自訂 assetlinks.json 內容 (JSON)", "layout": "排版", "hide-logo": "隱藏圖示", "hide-card-counter-list": "在「所有看板」隱藏卡片計數清單", @@ -979,8 +935,6 @@ "a-endAt": "修改結束時間", "a-startAt": "修改開始時間", "a-receivedAt": "修改接收時間", - "above-selected-card": "選取的卡片之上", - "below-selected-card": "選取的卡片之下", "almostdue": "當前到期時間%s即將到來", "pastdue": "當前到期時間%s已過", "duenow": "當前到期時間%s為今天", @@ -989,15 +943,15 @@ "act-almostdue": "__card__ 的當前到期提醒(__timeValue__) 正在接近", "act-pastdue": "__card__ 的當前到期提醒(__timeValue__) 已經過去了", "act-duenow": "__card__ 的當前到期提醒(__timeValue__) 現在到期", - "act-atUserComment": "提及您於卡片 __card__:__comment__ 在 __list__ 於泳道 __swimlane__ 在看板 __board__", + "act-atUserComment": "你在 [__board__] __list__/__card__ 被提到", "delete-user-confirm-popup": "確定要刪除此帳戶嗎?此操作無法還原。", "delete-team-confirm-popup": "確定要刪除此團隊? 此動作不能復原", "delete-org-confirm-popup": "確定刪除此組織? 此動作不能復原", "accounts-allowUserDelete": "允許用戶自行刪除其帳戶", "hide-minicard-label-text": "隱藏迷你卡片標籤內文", "show-desktop-drag-handles": "顯示桌面拖曳工具", - "assignee": "承辦人", - "cardAssigneesPopup-title": "承辦人", + "assignee": "代理人", + "cardAssigneesPopup-title": "代理人", "addmore-detail": "新增更多詳細描述", "show-on-card": "在卡片上顯示", "show-on-minicard": "在小卡片顯示", @@ -1013,7 +967,6 @@ "view-all": "檢視全部", "filter-by-unread": "篩選: 未讀", "mark-all-as-read": "標示全部已讀", - "mark-all-as-unread": "標記全部未讀", "remove-all-read": "移除所有已讀", "allow-rename": "允許更名", "allowRenamePopup-title": "允許更名", @@ -1048,10 +1001,6 @@ "person": "人物", "my-cards": "我的卡片", "card": "卡片", - "today": "今天", - "day": "天", - "week": "週", - "month": "月", "list": "清單", "board": "看板", "context-separator": "/", @@ -1098,7 +1047,7 @@ "operator-user-abbrev": "@", "operator-member": "成員", "operator-member-abbrev": "m", - "operator-assignee": "承辦人", + "operator-assignee": "代理人", "operator-assignee-abbrev": "a", "operator-creator": "建立者", "operator-status": "狀態", @@ -1129,7 +1078,7 @@ "predicate-checklist": "待辦清單", "predicate-start": "開始", "predicate-end": "完成", - "predicate-assignee": "承辦人", + "predicate-assignee": "代理人", "predicate-member": "成員", "predicate-public": "公開", "predicate-private": "私有", @@ -1154,10 +1103,10 @@ "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - 卡片評論包含 *<text>*.", "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - 卡片標籤要符合 *<color>* 或 *<name>", "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - 的簡寫 `__operator_label__:<color>` 或 `__operator_label__:<name>`", - "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - 卡片,其中 *<username>* 是 *成員* 或 *承辦人*", + "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - 卡片,其中 *<username>* 是 *成員* 或 *代理人*", "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - `user:<username>` 的簡寫", "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - 卡片,其中 *<username>* 是i *成員*", - "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - 卡片,其中 *<username>* 是 *承辦人*", + "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - 卡片,其中 *<username>* 是 *代理人*", "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - 卡片,其中 *<username>* 是卡片的建立者", "globalSearch-instructions-operator-org": "`__operator_org__:<display name|short name>` - 屬於分配給組織 *<name>* 看板的卡片", "globalSearch-instructions-operator-team": "`__operator_team__:<display name|short name>` - 屬於分配給團隊 *<name>* 看板的卡片", @@ -1229,9 +1178,9 @@ "subject": "主旨", "details": "內容", "carbon-copy": "副本 (Cc:)", - "ticket": "工單", - "tickets": "工單", - "ticket-number": "工單號碼", + "ticket": "工票", + "tickets": "工票", + "ticket-number": "工票號碼", "open": "開啟", "pending": "已延遲", "closed": "已關閉", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "隱藏所有待辦清單項目", "support": "支援", "supportPopup-title": "支援", - "support-page-enabled": "已啟用支援頁面", - "support-info-not-added-yet": "尚未新增支援資訊", - "support-info-only-for-logged-in-users": "支援資訊僅限已登入的使用者使用。", - "support-title": "支援標題", - "support-content": "支援內容", "accessibility": "無障礙", "accessibility-page-enabled": "已啟用無障礙頁面", "accessibility-info-not-added-yet": "尚未新增無障礙資訊", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "排程工作刪除成功", "cron-job-pause-failed": "暫停排程工作失敗", "cron-job-paused": "排程工作暫停失敗", - "cron-job-resume-failed": "未能還原排程工作", - "cron-job-resumed": "排程工作還原成功", - "cron-job-start-failed": "無法開始排程工作", - "cron-job-started": "排程工作成功開始", - "cron-migration-errors": "遷移錯誤", - "cron-migration-warnings": "遷移警告", - "cron-no-errors": "無錯誤可顯示", - "cron-error-severity": "嚴重性", - "cron-error-time": "時間", - "cron-error-message": "錯誤訊息", - "cron-error-details": "內容", - "cron-clear-errors": "清除所有錯誤", - "cron-retry-failed": "重試失敗的遷移", - "cron-resume-paused": "繼續暫停的遷移", - "cron-errors-cleared": "所有錯誤都已成功清除", - "cron-no-failed-migrations": "沒有需要重試的失敗遷移", - "cron-no-paused-migrations": "沒有需要繼續的暫停遷移", - "cron-migrations-resumed": "遷移已成功繼續", - "cron-migrations-retried": "失敗的遷移已成功重試", - "complete": "完成", - "idle": "閒置", "filesystem-path-description": "檔案儲存空間的基礎路徑", "gridfs-enabled": "已啟用 GridFS", "gridfs-enabled-description": "使用 MongoDB GridFS 作為檔案儲存空間", - "all-migrations": "所有遷移", - "select-migration": "選取遷移", - "start": "開始", - "pause": "暫停", - "stop": "停止", - "migration-starting": "開始遷移……", - "migration-pausing": "暫停遷移……", - "migration-stopping": "停止遷移……", "migration-pause-failed": "暫停遷移失敗", "migration-paused": "暫停遷移成功", "migration-progress": "遷移進度", "migration-start-failed": "開始遷移失敗", "migration-started": "開始遷移成功", - "migration-not-needed": "不需要遷移", "migration-status": "遷移狀態", "migration-stop-confirm": "您確定您想要停止所有遷移嗎?", "migration-stop-failed": "停止遷移失敗", @@ -1490,73 +1404,7 @@ "back-to-settings": "回到設定", "board-id": "看板 ID", "board-migration": "看板遷移", - "board-migrations": "看板遷移", "card-show-lists-on-minicard": "在迷你卡片上顯示清單", - "comprehensive-board-migration": "全面看板遷移", - "comprehensive-board-migration-description": "執行全面檢查與修復,確保看板資料完整性,包括清單排序、卡片位置及泳道結構。", - "delete-duplicate-empty-lists-migration": "刪除重複的空清單", - "delete-duplicate-empty-lists-migration-description": "安全地刪除空的重複清單。僅移除既無卡片、又存在標題相同且含卡片的另一份清單的清單。", - "lost-cards": "遺失的卡片", - "lost-cards-list": "已還原的項目", - "restore-lost-cards-migration": "還原遺失的卡片", - "restore-lost-cards-migration-description": "尋找並還原缺少泳道 ID 或清單 ID 的卡片與清單。建立「遺失的卡片」泳道,使所有遺失項目重新可見。", - "restore-all-archived-migration": "還原所有封存", - "restore-all-archived-migration-description": "還原所有已封存的泳道、清單與卡片。自動修復任何缺少泳道 ID 或清單 ID 的項目以使它們重新可見。", - "fix-missing-lists-migration": "修復遺失的清單", - "fix-missing-lists-migration-description": "偵測並修復在看板結構中遺失或損毀的清單。", - "fix-avatar-urls-migration": "修復大頭照 URL", - "fix-avatar-urls-migration-description": "更新看板成員的大頭照 URL 以使用正確的儲存空間後端並修復損壞的大頭照參照。", - "fix-all-file-urls-migration": "修復所有檔案 URL", - "fix-all-file-urls-migration-description": "更新所有此看板的檔案附件 URL 以使用正確的儲存空間後端並修復損壞的檔案參照。", - "migration-needed": "需要遷移", - "migration-complete": "完成", - "migration-running": "正在執行……", - "migration-successful": "遷移成功完成", - "migration-failed": "遷移失敗", - "migrations": "遷移", - "migrations-admin-only": "僅看板管理員可執行遷移", - "migrations-description": "為此看板執行資料完整性檢查並修復。每個遷移皆可單獨執行。", - "no-issues-found": "未找到問題", - "run-migration": "執行遷移", - "run-comprehensive-migration-confirm": "這將會執行全面的遷移以檢查並修復看板資料完整性。這可能需要數分鐘。要繼續嗎?", - "run-delete-duplicate-empty-lists-migration-confirm": "此操作將先將所有共享清單轉換為每個泳道專屬的清單,接著刪除那些存在標題相同且含卡片之重複清單的空清單。僅會移除真正冗餘的空清單。要繼續嗎?", - "run-restore-lost-cards-migration-confirm": "這將建立一個「遺失的卡片」泳道,並還原所有缺少泳道 ID 或清單 ID 的卡片與清單。此操作僅影響未封存項目。繼續?", - "run-restore-all-archived-migration-confirm": "此操作將還原所有已封存的泳道、清單及卡片,使其重新顯示。任何缺少 ID 的項目將自動修復。此操作無法輕易撤銷。要繼續嗎?", - "run-fix-missing-lists-migration-confirm": "這將會偵測並修復在看板結構中遺失或損毀的清單。要繼續嗎?", - "run-fix-avatar-urls-migration-confirm": "這將會更新看板成員的大頭照 URL 以使用正確的儲存空間後端。要繼續嗎?", - "run-fix-all-file-urls-migration-confirm": "這將會更新此看板上的所有檔案附件 URL 以使用正確的儲存空間後端。要繼續嗎?", - "restore-lost-cards-nothing-to-restore": "沒有需要還原的遺失泳道、清單或卡片", - - "migration-progress-title": "正在進行看板遷移", - "migration-progress-overall": "整體進度", - "migration-progress-current-step": "目前步驟", - "migration-progress-status": "狀態", - "migration-progress-details": "內容", - "migration-progress-note": "請稍候,我們正在將您的看板遷移至最新結構……", - "steps": "步進", - "view": "檢視", - "has-swimlanes": "有泳道", - - "step-analyze-board-structure": "分析看板結構", - "step-fix-orphaned-cards": "修復孤立卡片", - "step-convert-shared-lists": "轉換共享清單", - "step-ensure-per-swimlane-lists": "確保每個泳道專屬的清單", - "step-validate-migration": "驗證遷移", - "step-fix-avatar-urls": "修復大頭照 URL", - "step-fix-attachment-urls": "修復附件 URL", - "step-analyze-lists": "分析清單", - "step-create-missing-lists": "建立遺失的清單", - "step-update-cards": "更新卡片", - "step-finalize": "完成", - "step-delete-duplicate-empty-lists": "刪除重複的空清單", - "step-ensure-lost-cards-swimlane": "確保遺失的卡片泳道", - "step-restore-lists": "還原清單", - "step-restore-cards": "還原卡片", - "step-restore-swimlanes": "還原泳道", - "step-fix-missing-ids": "修復遺失的 ID", - "step-scan-users": "正在檢查看板成員大頭照", - "step-scan-files": "正在檢查看板檔案附件", - "step-fix-file-urls": "正在修復檔案 URL", "cleanup": "清理", "cleanup-old-jobs": "清理舊工作", "completed": "已完成", @@ -1637,7 +1485,6 @@ "schedule": "排程", "search-boards-or-operations": "搜尋看板或操作……", "show-list-on-minicard": "在迷你卡片顯示清單", - "showChecklistAtMinicard": "在迷你卡片上顯示檢查清單", "showing": "顯示", "start-test-operation": "開始測試操作", "start-time": "開始時間", @@ -1650,37 +1497,7 @@ "total-size": "總大小", "unmigrated-boards": "尚未遷移的看板", "weight": "權重", - "cron": "Cron", - "current-step": "目前步驟", - "otp": "OTP 代碼", - "create-account": "建立帳號", - "already-account": "已經有帳號了?登入", - "available-repositories": "可用倉庫", - "repositories": "倉庫", - "repository": "倉庫", - "repository-name": "倉庫名稱", - "size-bytes": "大小(位元組)", - "last-modified": "上次修改", - "no-repositories": "找不到倉庫", - "create-repository": "建立倉庫", - "upload-repository": "上傳/更新倉庫", - "api-endpoints": "API 端點", - "sign-in-to-upload": "登入以上傳倉庫", - "account-locked": "帳號因登入嘗試次數過多而暫時鎖定,請稍後再試。", - "otp-required": "OTP 代碼必填", - "invalid-credentials": "無效的使用者名稱或密碼", - "username-password-required": "使用者名稱與密碼必填", - "password-mismatch": "密碼不符", - "username-too-short": "使用者名稱必須至少 3 個字元", - "user-exists": "使用者已存在", - "account-created": "帳號已建立!您現在可以登入了。", - "account-creation-failed": "建立帳號失敗", - "login": "登入", - "confirm": "確認", - "error": "錯誤", - "file": "檔案", - "log": "紀錄檔", - "logout": "登出", - "server": "伺服器", - "protocol": "協定" + "idle": "閒置", + "complete": "完成", + "cron": "Cron" } diff --git a/imports/i18n/data/zh.i18n.json b/imports/i18n/data/zh.i18n.json index c4e6b2d4d..78fd22be4 100644 --- a/imports/i18n/data/zh.i18n.json +++ b/imports/i18n/data/zh.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/zh_SG.i18n.json b/imports/i18n/data/zh_SG.i18n.json deleted file mode 100644 index d9ef3e7d3..000000000 --- a/imports/i18n/data/zh_SG.i18n.json +++ /dev/null @@ -1,1686 +0,0 @@ -{ - "accept": "Accept", - "act-activity-notify": "Activity Notification", - "act-addAttachment": "added attachment __attachment__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteAttachment": "deleted attachment __attachment__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addSubtask": "added subtask __subtask__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addedLabel": "Added label __label__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removedLabel": "Removed label __label__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklist": "added checklist __checklist__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addChecklistItem": "added checklist item __checklistItem__ to checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklist": "removed checklist __checklist__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-removeChecklistItem": "removed checklist item __checklistItem__ from checklist __checkList__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-checkedItem": "checked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncheckedItem": "unchecked __checklistItem__ of checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-completeChecklist": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-uncompleteChecklist": "uncompleted checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-addComment": "commented on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-editComment": "edited comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-deleteComment": "deleted comment on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createBoard": "created board __board__", - "act-createSwimlane": "created swimlane __swimlane__ to board __board__", - "act-createCard": "created card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-createCustomField": "created custom field __customField__ at board __board__", - "act-deleteCustomField": "deleted custom field __customField__ at board __board__", - "act-setCustomField": "edited custom field __customField__: __customFieldValue__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-createList": "added list __list__ to board __board__", - "act-addBoardMember": "added member __member__ to board __board__", - "act-archivedBoard": "Board __board__ moved to Archive", - "act-archivedCard": "Card __card__ at list __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedList": "List __list__ at swimlane __swimlane__ at board __board__ moved to Archive", - "act-archivedSwimlane": "Swimlane __swimlane__ at board __board__ moved to Archive", - "act-importBoard": "imported board __board__", - "act-importCard": "imported card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-importList": "imported list __list__ to swimlane __swimlane__ at board __board__", - "act-joinMember": "added member __member__ to card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-moveCard": "moved card __card__ at board __board__ from list __oldList__ at swimlane __oldSwimlane__ to list __list__ at swimlane __swimlane__", - "act-moveCardToOtherBoard": "moved card __card__ from list __oldList__ at swimlane __oldSwimlane__ at board __oldBoard__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-removeBoardMember": "removed member __member__ from board __board__", - "act-restoredCard": "restored card __card__ to list __list__ at swimlane __swimlane__ at board __board__", - "act-unjoinMember": "removed member __member__ from card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "act-withBoardTitle": "__board__", - "act-withCardTitle": "[__board__] __card__", - "actions": "Actions", - "activities": "Activities", - "activity": "Activity", - "activity-added": "added %s to %s", - "activity-archived": "%s moved to Archive", - "activity-attached": "attached %s to %s", - "activity-created": "created %s", - "activity-changedListTitle": "renamed list to %s", - "activity-customfield-created": "created custom field %s", - "activity-excluded": "excluded %s from %s", - "activity-imported": "imported %s into %s from %s", - "activity-imported-board": "imported %s from %s", - "activity-joined": "joined %s", - "activity-moved": "moved %s from %s to %s", - "activity-on": "on %s", - "activity-removed": "removed %s from %s", - "activity-sent": "sent %s to %s", - "activity-unjoined": "unjoined %s", - "activity-subtask-added": "added subtask to %s", - "activity-checked-item": "checked %s in checklist %s of %s", - "activity-unchecked-item": "unchecked %s in checklist %s of %s", - "activity-checklist-added": "added checklist to %s", - "activity-checklist-removed": "removed a checklist from %s", - "activity-checklist-completed": "completed checklist %s of %s", - "activity-checklist-uncompleted": "uncompleted the checklist %s of %s", - "activity-checklist-item-added": "added checklist item to '%s' in %s", - "activity-checklist-item-removed": "removed a checklist item from '%s' in %s", - "add": "Add", - "activity-checked-item-card": "checked %s in checklist %s", - "activity-unchecked-item-card": "unchecked %s in checklist %s", - "activity-checklist-completed-card": "completed checklist __checklist__ at card __card__ at list __list__ at swimlane __swimlane__ at board __board__", - "activity-checklist-uncompleted-card": "uncompleted the checklist %s", - "activity-editComment": "edited comment %s", - "activity-deleteComment": "deleted comment %s", - "activity-receivedDate": "edited received date to %s of %s", - "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", - "activity-dueDate": "edited due date to %s of %s", - "activity-endDate": "edited end date to %s of %s", - "add-attachment": "Add Attachment", - "add-board": "Add Board", - "add-template": "Add Template", - "add-card": "Add Card", - "add-card-to-top-of-list": "Add Card to Top of List", - "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", - "setListWidthPopup-title": "Set Widths", - "set-list-width": "Set Widths", - "set-list-width-value": "Set Min & Max Widths (pixels)", - "list-width-error-message": "List widths must be integers greater than 100", - "keyboard-shortcuts-enabled": "Keyboard shortcuts enabled. Click to disable.", - "keyboard-shortcuts-disabled": "Keyboard shortcuts disabled. Click to enable.", - "setSwimlaneHeightPopup-title": "Set Swimlane Height", - "set-swimlane-height": "Set Swimlane Height", - "set-swimlane-height-value": "Swimlane Height (pixels)", - "swimlane-height-error-message": "Swimlane height must be a positive integer", - "add-swimlane": "Add Swimlane", - "add-subtask": "Add Subtask", - "add-checklist": "Add Checklist", - "add-checklist-item": "Add an item to checklist", - "close-add-checklist-item": "Close add an item to checklist form", - "close-edit-checklist-item": "Close edit an item to checklist form", - "convertChecklistItemToCardPopup-title": "Convert to Card", - "add-cover": "Add cover image to minicard", - "add-label": "Add Label", - "add-list": "Add List", - "add-after-list": "Add After List", - "add-members": "Add Members", - "added": "Added", - "addMemberPopup-title": "Add Members", - "memberPopup-title": "Member Settings", - "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", - "admin-announcement": "Announcement", - "admin-announcement-active": "Active System-Wide Announcement", - "admin-announcement-title": "Announcement from Administrator", - "all-boards": "All Boards", - "and-n-other-card": "And __count__ other card", - "and-n-other-card_plural": "And __count__ other cards", - "apply": "Apply", - "app-is-offline": "Loading, please wait. Refreshing the page will cause data loss. If loading does not work, please check that server has not stopped.", - "app-try-reconnect": "Try to reconnect.", - "archive": "Move to Archive", - "archive-all": "Move All to Archive", - "archive-board": "Move Board to Archive", - "archive-board-confirm": "Are you sure you want to archive this board?", - "archive-card": "Move Card to Archive", - "archive-list": "Move List to Archive", - "archive-swimlane": "Move Swimlane to Archive", - "archive-selection": "Move selection to Archive", - "archiveBoardPopup-title": "Move Board to Archive?", - "archived-items": "Archive", - "archived-boards": "Boards in Archive", - "restore-board": "Restore Board", - "no-archived-boards": "No Boards in Archive.", - "archives": "Archive", - "template": "Template", - "templates": "Templates", - "template-container": "Template Container", - "add-template-container": "Add Template Container", - "assign-member": "Assign member", - "attached": "attached", - "attachment": "Attachment", - "attachment-delete-pop": "Deleting an attachment is permanent. There is no undo.", - "attachmentDeletePopup-title": "Delete Attachment?", - "attachments": "Attachments", - "auto-watch": "Automatically watch boards when they are created", - "avatar-too-big": "The avatar is too large (__size__ max)", - "back": "Back", - "board-change-color": "Change color", - "board-change-background-image": "Change Background Image", - "board-background-image-url": "Background Image URL", - "add-background-image": "Add Background Image", - "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", - "boardInfoOnMyBoards-title": "All Boards Settings", - "show-card-counter-per-list": "Show card count per list", - "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", - "board-nb-stars": "%s stars", - "board-not-found": "Board not found", - "board-private-info": "This board will be <strong>private</strong>.", - "board-public-info": "This board will be <strong>public</strong>.", - "board-drag-drop-reorder-or-click-open": "Drag and drop to reorder board icons. Click board icon to open board.", - "boardChangeColorPopup-title": "Change Board Background", - "boardChangeBackgroundImagePopup-title": "Change Background Image", - "allBoardsChangeColorPopup-title": "Change color", - "allBoardsChangeBackgroundImagePopup-title": "Change Background Image", - "boardChangeTitlePopup-title": "Rename Board", - "boardChangeVisibilityPopup-title": "Change Visibility", - "boardChangeWatchPopup-title": "Change Watch", - "boardMenuPopup-title": "Board Settings", - "allBoardsMenuPopup-title": "Settings", - "boardChangeViewPopup-title": "Board View", - "boards": "Boards", - "board-view": "Board View", - "desktop-mode": "Desktop Mode", - "mobile-mode": "Mobile Mode", - "mobile-desktop-toggle": "Toggle between Mobile and Desktop Mode", - "zoom-in": "Zoom In", - "zoom-out": "Zoom Out", - "click-to-change-zoom": "Click to change zoom level", - "zoom-level": "Zoom Level", - "enter-zoom-level": "Enter zoom level (50-300%):", - "board-view-cal": "Calendar", - "board-view-swimlanes": "Swimlanes", - "board-view-collapse": "Collapse", - "board-view-gantt": "Gantt", - "board-view-lists": "Lists", - "bucket-example": "Like \"Bucket List\" for example", - "calendar-previous-month-label": "Previous Month", - "calendar-next-month-label": "Next Month", - "cancel": "Cancel", - "card-archived": "This card is moved to Archive.", - "board-archived": "This board is moved to Archive.", - "card-comments-title": "This card has %s comment.", - "card-delete-notice": "Deleting is permanent. You will lose all actions associated with this card.", - "card-delete-pop": "All actions will be removed from the activity feed and you won't be able to re-open the card. There is no undo.", - "card-delete-suggest-archive": "You can move a card to Archive to remove it from the board and preserve the activity.", - "card-archive-pop": "Card will not be visible at this list after archiving card.", - "card-archive-suggest-cancel": "You can later restore card from Archive.", - "card-due": "Due", - "card-due-on": "Due on", - "card-spent": "Spent Time", - "card-edit-attachments": "Edit attachments", - "card-edit-custom-fields": "Edit custom fields", - "card-edit-labels": "Edit labels", - "card-edit-members": "Edit members", - "card-labels-title": "Change the labels for the card.", - "card-members-title": "Add or remove members of the board from the card.", - "card-start": "Start", - "card-start-on": "Starts on", - "cardAttachmentsPopup-title": "Attach From", - "cardCustomField-datePopup-title": "Change date", - "cardCustomFieldsPopup-title": "Edit custom fields", - "cardStartVotingPopup-title": "Start a vote", - "positiveVoteMembersPopup-title": "Proponents", - "negativeVoteMembersPopup-title": "Opponents", - "card-edit-voting": "Edit voting", - "editVoteEndDatePopup-title": "Change vote end date", - "allowNonBoardMembers": "Allow all logged in users", - "vote-question": "Voting question", - "vote-public": "Show who voted what", - "vote-for-it": "for it", - "vote-against": "against", - "deleteVotePopup-title": "Delete vote?", - "vote-delete-pop": "Deleting is permanent. You will lose all actions associated with this vote.", - "cardStartPlanningPokerPopup-title": "Start a Planning Poker", - "card-edit-planning-poker": "Edit Planning Poker", - "editPokerEndDatePopup-title": "Change Planning Poker vote end date", - "poker-question": "Planning Poker", - "poker-one": "1", - "poker-two": "2", - "poker-three": "3", - "poker-five": "5", - "poker-eight": "8", - "poker-thirteen": "13", - "poker-twenty": "20", - "poker-forty": "40", - "poker-oneHundred": "100", - "poker-unsure": "?", - "poker-finish": "Finish", - "poker-result-votes": "Votes", - "poker-result-who": "Who", - "poker-replay": "Replay", - "set-estimation": "Set Estimation", - "deletePokerPopup-title": "Delete planning poker?", - "poker-delete-pop": "Deleting is permanent. You will lose all actions associated with this planning poker.", - "cardDeletePopup-title": "Delete Card?", - "cardArchivePopup-title": "Archive Card?", - "cardDetailsActionsPopup-title": "Card Actions", - "cardLabelsPopup-title": "Labels", - "cardMembersPopup-title": "Members", - "cardMorePopup-title": "More", - "cardTemplatePopup-title": "Create template", - "cards": "Cards", - "cards-count": "Cards", - "cards-count-one": "Card", - "casSignIn": "Sign In with CAS", - "cardType-card": "Card", - "cardType-linkedCard": "Linked Card", - "cardType-linkedBoard": "Linked Board", - "change": "Change", - "change-avatar": "Change Avatar", - "change-password": "Change Password", - "change-permissions": "Change permissions", - "change-settings": "Change Settings", - "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", - "changeLanguagePopup-title": "Change Language", - "changePasswordPopup-title": "Change Password", - "changePermissionsPopup-title": "Change Permissions", - "changeSettingsPopup-title": "Change Settings", - "subtasks": "Subtasks", - "checklists": "Checklists", - "click-to-star": "Click to star this board.", - "click-to-unstar": "Click to unstar this board.", - "click-to-enable-auto-width": "Auto list width disabled. Click to enable.", - "click-to-disable-auto-width": "Auto list width enabled. Click to disable.", - "auto-list-width": "Auto list width", - "clipboard": "Clipboard or drag & drop", - "close": "Close", - "close-board": "Close Board", - "close-board-pop": "You will be able to restore the board by clicking the “Archive” button from the home header.", - "close-card": "Close Card", - "color-black": "black", - "color-blue": "blue", - "color-crimson": "crimson", - "color-darkgreen": "darkgreen", - "color-gold": "gold", - "color-gray": "gray", - "color-green": "green", - "color-indigo": "indigo", - "color-lime": "lime", - "color-magenta": "magenta", - "color-mistyrose": "mistyrose", - "color-navy": "navy", - "color-orange": "orange", - "color-paleturquoise": "paleturquoise", - "color-peachpuff": "peachpuff", - "color-pink": "pink", - "color-plum": "plum", - "color-purple": "purple", - "color-red": "red", - "color-saddlebrown": "saddlebrown", - "color-silver": "silver", - "color-sky": "sky", - "color-slateblue": "slateblue", - "color-white": "white", - "color-yellow": "yellow", - "unset-color": "Unset", - "comments": "Comments", - "comment": "Comment", - "comment-placeholder": "Write Comment", - "comment-only": "Comment only", - "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", - "comment-delete": "Are you sure you want to delete the comment?", - "deleteCommentPopup-title": "Delete comment?", - "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", - "worker": "Worker", - "worker-desc": "Can only move cards, assign itself to card and comment.", - "computer": "Computer", - "confirm-subtask-delete-popup": "Are you sure you want to delete subtask?", - "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", - "subtaskDeletePopup-title": "Delete Subtask?", - "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", - "copy-card-link-to-clipboard": "Copy card link to clipboard", - "copy-text-to-clipboard": "Copy text to clipboard", - "linkCardPopup-title": "Link Card", - "searchElementPopup-title": "Search", - "copyCardPopup-title": "Copy Card", - "copyManyCardsPopup-title": "Copy Template to Many Cards", - "copyManyCardsPopup-instructions": "Destination Card Titles and Descriptions in this JSON format", - "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", - "create": "Create", - "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", - "chooseBoardSourcePopup-title": "Import board", - "createLabelPopup-title": "Create Label", - "createCustomField": "Create Field", - "createCustomFieldPopup-title": "Create Field", - "current": "current", - "custom-field-delete-pop": "There is no undo. This will remove this custom field from all cards and destroy its history.", - "custom-field-checkbox": "Checkbox", - "custom-field-currency": "Currency", - "custom-field-currency-option": "Currency Code", - "custom-field-date": "Date", - "custom-field-dropdown": "Dropdown List", - "custom-field-dropdown-none": "(none)", - "custom-field-dropdown-options": "List Options", - "custom-field-dropdown-options-placeholder": "Press enter to add more options", - "custom-field-dropdown-unknown": "(unknown)", - "custom-field-number": "Number", - "custom-field-text": "Text", - "custom-fields": "Custom Fields", - "date": "Date", - "date-format": "Date Format", - "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", - "date-format-mm-dd-yyyy": "MM-DD-YYYY", - "decline": "Decline", - "default-avatar": "Default avatar", - "delete": "Delete", - "deleteCustomFieldPopup-title": "Delete Custom Field?", - "deleteLabelPopup-title": "Delete Label?", - "description": "Description", - "disambiguateMultiLabelPopup-title": "Disambiguate Label Action", - "disambiguateMultiMemberPopup-title": "Disambiguate Member Action", - "discard": "Discard", - "done": "Done", - "download": "Download", - "edit": "Edit", - "edit-avatar": "Change Avatar", - "edit-profile": "Edit Profile", - "edit-wip-limit": "Edit WIP Limit", - "soft-wip-limit": "Soft WIP Limit", - "editCardStartDatePopup-title": "Change start date", - "editCardDueDatePopup-title": "Change due date", - "editCustomFieldPopup-title": "Edit Field", - "addReactionPopup-title": "Add reaction", - "editCardSpentTimePopup-title": "Change spent time", - "editLabelPopup-title": "Change Label", - "editNotificationPopup-title": "Edit Notification", - "editProfilePopup-title": "Edit Profile", - "email": "Email", - "email-address": "Email Address", - "email-enrollAccount-subject": "An account created for you on __siteName__", - "email-enrollAccount-text": "Hello __user__,\n\nTo start using the service, simply click the link below.\n\n__url__\n\nThanks.", - "email-fail": "Sending email failed", - "email-fail-text": "Error trying to send email", - "email-invalid": "Invalid email", - "email-invite": "Invite via Email", - "email-invite-subject": "__inviter__ sent you an invitation", - "email-invite-text": "Dear __user__,\n\n__inviter__ invites you to join board \"__board__\" for collaborations.\n\nPlease follow the link below:\n\n__url__\n\nThanks.", - "email-resetPassword-subject": "Reset your password on __siteName__", - "email-resetPassword-text": "Hello __user__,\n\nTo reset your password, simply click the link below.\n\n__url__\n\nThanks.", - "email-sent": "Email sent", - "email-verifyEmail-subject": "Verify your email address on __siteName__", - "email-verifyEmail-text": "Hello __user__,\n\nTo verify your account email, simply click the link below.\n\n__url__\n\nThanks.", - "enable-vertical-scrollbars": "Enable vertical scrollbars", - "enable-wip-limit": "Enable WIP Limit", - "error-board-doesNotExist": "This board does not exist", - "error-board-notAdmin": "You need to be admin of this board to do that", - "error-board-notAMember": "You need to be a member of this board to do that", - "error-json-malformed": "Your text is not valid JSON", - "error-json-schema": "Your JSON data does not include the proper information in the correct format", - "error-csv-schema": "Your CSV(Comma Separated Values)/TSV (Tab Separated Values) does not include the proper information in the correct format ", - "error-list-doesNotExist": "This list does not exist", - "error-user-doesNotExist": "This user does not exist", - "error-user-notAllowSelf": "You can not invite yourself", - "error-user-notCreated": "This user is not created", - "error-username-taken": "This username is already taken", - "error-orgname-taken": "This organization name is already taken", - "error-teamname-taken": "This team name is already taken", - "error-email-taken": "Email has already been taken", - "export-board": "Export board", - "export-board-json": "Export board to JSON", - "export-board-csv": "Export board to CSV", - "export-board-tsv": "Export board to TSV", - "export-board-excel": "Export board to Excel", - "user-can-not-export-excel": "User can not export Excel", - "export-board-html": "Export board to HTML", - "export-card": "Export card", - "export-card-pdf": "Export card to PDF", - "user-can-not-export-card-to-pdf": "User can not export card to PDF", - "exportBoardPopup-title": "Export board", - "exportCardPopup-title": "Export card", - "sort": "Sort", - "sorted": "Sorted", - "remove-sort": "Remove sort", - "sort-desc": "Click to Sort List", - "list-sort-by": "Sort the List By:", - "list-label-modifiedAt": "Last Access Time", - "list-label-title": "Name of the List", - "list-label-sort": "Your Manual Order", - "list-label-short-modifiedAt": "(L)", - "list-label-short-title": "(N)", - "list-label-short-sort": "(M)", - "filter": "Filter", - "filter-cards": "Filter Cards or Lists", - "filter-dates-label": "Filter by date", - "filter-no-due-date": "No due date", - "filter-overdue": "Overdue", - "filter-due-today": "Due today", - "filter-due-this-week": "Due this week", - "filter-due-next-week": "Due next week", - "filter-due-tomorrow": "Due tomorrow", - "list-filter-label": "Filter List by Title", - "filter-clear": "Clear filter", - "filter-labels-label": "Filter by label", - "filter-no-label": "No label", - "filter-member-label": "Filter by member", - "filter-no-member": "No member", - "filter-assignee-label": "Filter by assignee", - "filter-no-assignee": "No assignee", - "filter-custom-fields-label": "Filter by Custom Fields", - "filter-no-custom-fields": "No Custom Fields", - "filter-show-archive": "Show archived lists", - "filter-hide-empty": "Hide empty lists", - "filter-on": "Filter is on", - "filter-on-desc": "You are filtering cards on this board. Click here to edit filter.", - "filter-to-selection": "Filter to selection", - "other-filters-label": "Other Filters", - "advanced-filter-label": "Advanced Filter", - "advanced-filter-description": "Advanced Filter allows to write a string containing following operators: == != <= >= && || ( ) A space is used as a separator between the Operators. You can filter for all Custom Fields by typing their names and values. For Example: Field1 == Value1. Note: If fields or values contains spaces, you need to encapsulate them into single quotes. For Example: 'Field 1' == 'Value 1'. For single control characters (' \\/) to be skipped, you can use \\. For example: Field1 == I\\'m. Also you can combine multiple conditions. For Example: F1 == V1 || F1 == V2. Normally all operators are interpreted from left to right. You can change the order by placing brackets. For Example: F1 == V1 && ( F2 == V2 || F2 == V3 ). Also you can search text fields using regex: F1 == /Tes.*/i", - "fullname": "Full Name", - "header-logo-title": "Go back to your boards page.", - "show-activities": "Show Activities", - "headerBarCreateBoardPopup-title": "Create Board", - "home": "Home", - "import": "Import", - "impersonate-user": "Impersonate user", - "link": "Link", - "import-board": "import board", - "import-board-c": "Import board", - "import-board-title-trello": "Import board from Trello", - "import-board-title-wekan": "Import board from previous export", - "import-board-title-csv": "Import board from CSV/TSV", - "from-trello": "From Trello", - "from-wekan": "From previous export", - "from-csv": "From CSV/TSV", - "import-board-instruction-trello": "In your Trello board, go to 'Menu', then 'More', 'Print and Export', 'Export JSON', and copy the resulting text.", - "import-board-instruction-csv": "Paste in your Comma Separated Values(CSV)/ Tab Separated Values (TSV) .", - "import-board-instruction-wekan": "In your board, go to 'Menu', then 'Export board', and copy the text in the downloaded file.", - "import-board-instruction-about-errors": "If you get errors when importing board, sometimes importing still works, and board is at All Boards page.", - "import-json-placeholder": "Paste your valid JSON data here", - "import-csv-placeholder": "Paste your valid CSV/TSV data here", - "import-map-members": "Map members", - "import-members-map": "Your imported board has some members. Please map the members you want to import to your users", - "import-members-map-note": "Note: Unmapped members will be assigned to the current user.", - "import-show-user-mapping": "Review members mapping", - "import-user-select": "Pick your existing user you want to use as this member", - "importMapMembersAddPopup-title": "Select member", - "info": "Version", - "initials": "Initials", - "invalid-date": "Invalid date", - "invalid-time": "Invalid time", - "invalid-user": "Invalid user", - "joined": "joined", - "just-invited": "You are just invited to this board", - "keyboard-shortcuts": "Keyboard shortcuts", - "label-create": "Create Label", - "label-default": "%s label (default)", - "label-delete-pop": "There is no undo. This will remove this label from all cards and destroy its history.", - "labels": "Labels", - "language": "Language", - "last-admin-desc": "You can’t change roles because there must be at least one admin.", - "leave-board": "Leave Board", - "leave-board-pop": "Are you sure you want to leave __boardTitle__? You will be removed from all cards on this board.", - "leaveBoardPopup-title": "Leave Board ?", - "link-card": "Link to this card", - "list-archive-cards": "Move all cards in this list to Archive", - "list-archive-cards-pop": "This will remove all the cards in this list from the board. To view cards in Archive and bring them back to the board, click “Menu” > “Archive”.", - "list-move-cards": "Move all cards in this list", - "list-select-cards": "Select all cards in this list", - "set-color-list": "Set Color", - "listActionPopup-title": "List Actions", - "settingsUserPopup-title": "User Settings", - "settingsTeamPopup-title": "Team Settings", - "settingsOrgPopup-title": "Organization Settings", - "swimlaneActionPopup-title": "Swimlane Actions", - "swimlaneAddPopup-title": "Add a Swimlane below", - "listImportCardPopup-title": "Import a Trello card", - "listImportCardsTsvPopup-title": "Import Excel CSV/TSV", - "listMorePopup-title": "More", - "link-list": "Link to this list", - "list-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the list. There is no undo.", - "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", - "lists": "Lists", - "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", - "log-out": "Log Out", - "log-in": "Log In", - "loginPopup-title": "Log In", - "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", - "members": "Members", - "menu": "Menu", - "move-selection": "Move selection", - "moveCardPopup-title": "Move Card", - "moveCardToBottom-title": "Move to Bottom", - "moveCardToTop-title": "Move to Top", - "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", - "multi-selection": "Multi-Selection", - "multi-selection-label": "Set label for selection", - "multi-selection-member": "Set member for selection", - "multi-selection-on": "Multi-Selection is on", - "muted": "Muted", - "muted-info": "You will never be notified of any changes in this board", - "my-boards": "My Boards", - "name": "Name", - "no-archived-cards": "No cards in Archive.", - "no-archived-lists": "No lists in Archive.", - "no-archived-swimlanes": "No swimlanes in Archive.", - "no-results": "No results", - "normal": "Normal", - "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", - "not-accepted-yet": "Invitation not accepted yet", - "notify-participate": "Receive updates to any cards you participate as creator or member", - "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", - "optional": "optional", - "or": "or", - "page-maybe-private": "This page may be private. You may be able to view it by <a href='%s'>logging in</a>.", - "page-not-found": "Page not found.", - "password": "Password", - "paste-or-dragdrop": "to paste, or drag & drop image file to it (image only)", - "participating": "Participating", - "preview": "Preview", - "previewAttachedImagePopup-title": "Preview", - "previewClipboardImagePopup-title": "Preview", - "private": "Private", - "private-desc": "This board is private. Only people added to the board can view and edit it.", - "profile": "Profile", - "public": "Public", - "public-desc": "This board is public. It's visible to anyone with the link and will show up in search engines like Google. Only people added to the board can edit.", - "quick-access-description": "Star a board to add a shortcut in this bar.", - "remove-cover": "Remove cover image from minicard", - "remove-from-board": "Remove from Board", - "remove-label": "Remove Label", - "listDeletePopup-title": "Delete List ?", - "remove-member": "Remove Member", - "remove-member-from-card": "Remove from Card", - "remove-member-pop": "Remove __name__ (__username__) from __boardTitle__? The member will be removed from all cards on this board. They will receive a notification.", - "removeMemberPopup-title": "Remove Member?", - "rename": "Rename", - "rename-board": "Rename Board", - "restore": "Restore", - "rescue-card-description": "Show rescue dialogue before closing for unsaved card descriptions", - "rescue-card-description-dialogue": "Overwrite current card description with your changes?", - "save": "Save", - "search": "Search", - "rules": "Rules", - "search-cards": "Search from card/list titles, descriptions and custom fields on this board", - "search-example": "Write text you search and press Enter", - "select-color": "Select Color", - "select-board": "Select Board", - "set-wip-limit-value": "Set a limit for the maximum number of tasks in this list", - "setWipLimitPopup-title": "Set WIP Limit", - "shortcut-add-self": "Add yourself to current card", - "shortcut-assign-self": "Assign yourself to current card", - "shortcut-autocomplete-emoji": "Autocomplete emoji", - "shortcut-autocomplete-members": "Autocomplete members", - "shortcut-clear-filters": "Clear all filters", - "shortcut-close-dialog": "Close Dialog", - "shortcut-filter-my-cards": "Filter my cards", - "shortcut-filter-my-assigned-cards": "Filter my assigned cards", - "shortcut-show-shortcuts": "Bring up this shortcuts list", - "shortcut-toggle-filterbar": "Toggle Filter Sidebar", - "shortcut-toggle-searchbar": "Toggle Search Sidebar", - "shortcut-toggle-sidebar": "Toggle Board Sidebar", - "show-cards-minimum-count": "Show cards count if list contains more than", - "sidebar-open": "Open Sidebar", - "sidebar-close": "Close Sidebar", - "signupPopup-title": "Create an Account", - "star-board-title": "Click to star this board. It will show up at top of your boards list.", - "starred-boards": "Starred Boards", - "starred-boards-description": "Starred boards show up at the top of your boards list.", - "subscribe": "Subscribe", - "team": "Team", - "this-board": "this board", - "this-card": "this card", - "spent-time-hours": "Spent time (hours)", - "overtime-hours": "Overtime (hours)", - "overtime": "Overtime", - "has-overtime-cards": "Has overtime cards", - "has-spenttime-cards": "Has spent time cards", - "time": "Time", - "title": "Title", - "toggle-assignees": "Toggle assignees 1-9 for card (By order of addition to board).", - "toggle-labels": "Toggle labels 1-9 for card. Multi-Selection adds labels 1-9", - "remove-labels-multiselect": "Multi-Selection removes labels 1-9", - "tracking": "Tracking", - "tracking-info": "You will be notified of any changes to those cards you are involved as creator or member.", - "type": "Type", - "unassign-member": "Unassign member", - "unsaved-description": "You have an unsaved description.", - "unwatch": "Unwatch", - "upload": "Upload", - "upload-avatar": "Upload an avatar", - "uploaded-avatar": "Uploaded an avatar", - "uploading-files": "Uploading files", - "upload-failed": "Upload failed", - "upload-completed": "Upload completed", - "custom-top-left-corner-logo-image-url": "Custom Top Left Corner Logo Image URL", - "custom-top-left-corner-logo-link-url": "Custom Top Left Corner Logo Link URL", - "custom-top-left-corner-logo-height": "Custom Top Left Corner Logo Height. Default: 27", - "custom-login-logo-image-url": "Custom Login Logo Image URL", - "custom-login-logo-link-url": "Custom Login Logo Link URL", - "custom-help-link-url": "Custom Help Link URL", - "text-below-custom-login-logo": "Text below Custom Login Logo", - "automatic-linked-url-schemes": "Custom URL Schemes which should automatically be clickable. One URL Scheme per line", - "username": "Username", - "import-usernames": "Import Usernames", - "view-it": "View it", - "warn-list-archived": "warning: this card is in an list at Archive", - "watch": "Watch", - "watching": "Watching", - "watching-info": "You will be notified of any change in this board", - "welcome-board": "Welcome Board", - "welcome-swimlane": "Milestone 1", - "welcome-list1": "Basics", - "welcome-list2": "Advanced", - "card-templates-swimlane": "Card Templates", - "list-templates-swimlane": "List Templates", - "board-templates-swimlane": "Board Templates", - "what-to-do": "What do you want to do?", - "wipLimitErrorPopup-title": "Invalid WIP Limit", - "wipLimitErrorPopup-dialog-pt1": "The number of tasks in this list is higher than the WIP limit you've defined.", - "wipLimitErrorPopup-dialog-pt2": "Please move some tasks out of this list, or set a higher WIP limit.", - "admin-panel": "Admin Panel", - "settings": "Settings", - "people": "People", - "registration": "Registration", - "disable-self-registration": "Disable Self-Registration", - "disable-forgot-password": "Disable Forgot Password", - "invite": "Invite", - "invite-people": "Invite People", - "to-boards": "To board(s)", - "email-addresses": "Email Addresses", - "smtp-host-description": "The address of the SMTP server that handles your emails.", - "smtp-port-description": "The port your SMTP server uses for outgoing emails.", - "smtp-tls-description": "Enable TLS support for SMTP server", - "smtp-host": "SMTP Host", - "smtp-port": "SMTP Port", - "smtp-username": "Username", - "smtp-password": "Password", - "smtp-tls": "TLS support", - "send-from": "From", - "send-smtp-test": "Send a test email to yourself", - "invitation-code": "Invitation Code", - "email-invite-register-subject": "__inviter__ sent you an invitation", - "email-invite-register-text": "Dear __user__,\n\n__inviter__ invites you to kanban board for collaborations.\n\nPlease follow the link below:\n__url__\n\nAnd your invitation code is: __icode__\n\nThanks.", - "email-smtp-test-subject": "SMTP Test Email", - "email-smtp-test-text": "You have successfully sent an email", - "error-invitation-code-not-exist": "Invitation code doesn't exist", - "error-notAuthorized": "You are not authorized to view this page.", - "webhook-title": "Webhook Name", - "webhook-token": "Token (Optional for Authentication)", - "outgoing-webhooks": "Outgoing Webhooks", - "bidirectional-webhooks": "Two-Way Webhooks", - "outgoingWebhooksPopup-title": "Outgoing Webhooks", - "boardCardTitlePopup-title": "Card Title Filter", - "disable-webhook": "Disable This Webhook", - "global-webhook": "Global Webhooks", - "new-outgoing-webhook": "New Outgoing Webhook", - "no-name": "(Unknown)", - "Node_version": "Node version", - "Meteor_version": "Meteor version", - "MongoDB_version": "MongoDB version", - "MongoDB_storage_engine": "MongoDB storage engine", - "MongoDB_Oplog_enabled": "MongoDB Oplog enabled", - "OS_Arch": "OS Arch", - "OS_Cpus": "OS CPU Count", - "OS_Freemem": "OS Free Memory", - "OS_Loadavg": "OS Load Average", - "OS_Platform": "OS Platform", - "OS_Release": "OS Release", - "OS_Totalmem": "OS Total Memory", - "OS_Type": "OS Type", - "OS_Uptime": "OS Uptime", - "days": "days", - "hours": "hours", - "minutes": "minutes", - "seconds": "seconds", - "show-field-on-card": "Show this field on card", - "automatically-field-on-card": "Add field to new cards", - "always-field-on-card": "Add field to all cards", - "showLabel-field-on-card": "Show field label on minicard", - "showSum-field-on-list": "Show sum of fields at top of list", - "yes": "Yes", - "no": "No", - "accounts": "Accounts", - "accounts-allowEmailChange": "Allow Email Change", - "accounts-allowUserNameChange": "Allow Username Change", - "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", - "createdAt": "Created at", - "modifiedAt": "Modified at", - "verified": "Verified", - "active": "Active", - "card-received": "Received", - "card-received-on": "Received on", - "card-end": "End", - "card-end-on": "Ends on", - "editCardReceivedDatePopup-title": "Change received date", - "editCardEndDatePopup-title": "Change end date", - "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", - "setCardActionsColorPopup-title": "Choose a color", - "setSwimlaneColorPopup-title": "Choose a color", - "setListColorPopup-title": "Choose a color", - "assigned-by": "Assigned By", - "requested-by": "Requested By", - "card-sorting-by-number": "Card sorting by number", - "board-delete-notice": "Deleting is permanent. You will lose all lists, cards and actions associated with this board.", - "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", - "boardDeletePopup-title": "Delete Board?", - "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", - "delete-duplicate-lists": "Delete Duplicate Lists", - "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", - "default-subtasks-board": "Subtasks for __board__ board", - "default": "Default", - "defaultdefault": "Default", - "queue": "Queue", - "subtask-settings": "Subtasks Settings", - "card-settings": "Card Settings", - "minicard-settings": "Minicard Settings", - "boardSubtaskSettingsPopup-title": "Subtasks Settings", - "boardCardSettingsPopup-title": "Card Settings", - "boardMinicardSettingsPopup-title": "Minicard Settings", - "deposit-subtasks-board": "Deposit subtasks to this board:", - "deposit-subtasks-list": "Landing list for subtasks deposited here:", - "show-parent-in-minicard": "Show parent in minicard:", - "description-on-minicard": "Description on minicard", - "cover-attachment-on-minicard": "Cover image on minicard", - "badge-attachment-on-minicard": "Count of attachments on minicard", - "card-sorting-by-number-on-minicard": "Card sorting by number on minicard", - "prefix-with-full-path": "Prefix with full path", - "prefix-with-parent": "Prefix with parent", - "subtext-with-full-path": "Subtext with full path", - "subtext-with-parent": "Subtext with parent", - "change-card-parent": "Change card's parent", - "parent-card": "Parent card", - "source-board": "Source board", - "no-parent": "Don't show parent", - "activity-added-label": "added label '%s' to %s", - "activity-removed-label": "removed label '%s' from %s", - "activity-delete-attach": "deleted an attachment from %s", - "activity-added-label-card": "added label '%s'", - "activity-removed-label-card": "removed label '%s'", - "activity-delete-attach-card": "deleted an attachment", - "activity-set-customfield": "set custom field '%s' to '%s' in %s", - "activity-unset-customfield": "unset custom field '%s' in %s", - "r-rule": "Rule", - "r-add-trigger": "Add trigger", - "r-add-action": "Add action", - "r-board-rules": "Board rules", - "r-add-rule": "Add rule", - "r-view-rule": "View rule", - "r-delete-rule": "Delete rule", - "r-new-rule-name": "New rule title", - "r-no-rules": "No rules", - "r-trigger": "Trigger", - "r-action": "Action", - "r-when-a-card": "When a card", - "r-is": "is", - "r-is-moved": "is moved", - "r-added-to": "Added to", - "r-removed-from": "Removed from", - "r-the-board": "the board", - "r-list": "list", - "set-filter": "Set Filter", - "r-moved-to": "Moved to", - "r-moved-from": "Moved from", - "r-archived": "Moved to Archive", - "r-unarchived": "Restored from Archive", - "r-a-card": "a card", - "r-when-a-label-is": "When a label is", - "r-when-the-label": "When the label", - "r-list-name": "list name", - "r-when-a-member": "When a member is", - "r-when-the-member": "When the member", - "r-name": "name", - "r-when-a-attach": "When an attachment", - "r-when-a-checklist": "When a checklist is", - "r-when-the-checklist": "When the checklist", - "r-completed": "Completed", - "r-made-incomplete": "Made incomplete", - "r-when-a-item": "When a checklist item is", - "r-when-the-item": "When the checklist item", - "r-checked": "Checked", - "r-unchecked": "Unchecked", - "r-move-card-to": "Move card to", - "r-top-of": "Top of", - "r-bottom-of": "Bottom of", - "r-its-list": "its list", - "r-archive": "Move to Archive", - "r-unarchive": "Restore from Archive", - "r-card": "card", - "r-add": "Add", - "r-remove": "Remove", - "r-label": "label", - "r-member": "member", - "r-remove-all": "Remove all members from the card", - "r-set-color": "Set color to", - "r-checklist": "checklist", - "r-check-all": "Check all", - "r-uncheck-all": "Uncheck all", - "r-items-check": "items of checklist", - "r-check": "Check", - "r-uncheck": "Uncheck", - "r-item": "item", - "r-of-checklist": "of checklist", - "r-send-email": "Send an email", - "r-to": "to", - "r-of": "of", - "r-subject": "subject", - "r-rule-details": "Rule details", - "r-d-move-to-top-gen": "Move card to top of its list", - "r-d-move-to-top-spec": "Move card to top of list", - "r-d-move-to-bottom-gen": "Move card to bottom of its list", - "r-d-move-to-bottom-spec": "Move card to bottom of list", - "r-d-send-email": "Send email", - "r-d-send-email-to": "to", - "r-d-send-email-subject": "subject", - "r-d-send-email-message": "message", - "r-d-archive": "Move card to Archive", - "r-d-unarchive": "Restore card from Archive", - "r-d-add-label": "Add label", - "r-d-remove-label": "Remove label", - "r-create-card": "Create new card", - "r-in-list": "in list", - "r-in-swimlane": "in swimlane", - "r-d-add-member": "Add member", - "r-d-remove-member": "Remove member", - "r-d-remove-all-member": "Remove all member", - "r-d-check-all": "Check all items of a list", - "r-d-uncheck-all": "Uncheck all items of a list", - "r-d-check-one": "Check item", - "r-d-uncheck-one": "Uncheck item", - "r-d-check-of-list": "of checklist", - "r-d-add-checklist": "Add checklist", - "r-d-remove-checklist": "Remove checklist", - "r-by": "by", - "r-add-checklist": "Add checklist", - "r-with-items": "with items", - "r-items-list": "item1,item2,item3", - "r-add-swimlane": "Add swimlane", - "r-swimlane-name": "swimlane name", - "r-board-note": "Note: leave a field empty to match every possible value. ", - "r-checklist-note": "Note: checklist's items have to be written as comma separated values.", - "r-when-a-card-is-moved": "When a card is moved to another list", - "r-set": "Set", - "r-update": "Update", - "r-datefield": "date field", - "r-df-start-at": "start", - "r-df-due-at": "due", - "r-df-end-at": "end", - "r-df-received-at": "received", - "r-to-current-datetime": "to current date/time", - "r-remove-value-from": "Remove value from", - "r-link-card": "Link card to", - "ldap": "LDAP", - "oauth2": "OAuth2", - "cas": "CAS", - "authentication-method": "Authentication method", - "authentication-type": "Authentication type", - "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", - "layout": "Layout", - "hide-logo": "Hide Logo", - "hide-card-counter-list": "Hide card counter list on All Boards", - "hide-board-member-list": "Hide board member list on All Boards", - "add-custom-html-after-body-start": "Add Custom HTML after <body> start", - "add-custom-html-before-body-end": "Add Custom HTML before </body> end", - "error-undefined": "Something went wrong", - "error-ldap-login": "An error occurred while trying to login", - "display-authentication-method": "Display Authentication Method", - "oidc-button-text": "Customize the OIDC button text", - "default-authentication-method": "Default Authentication Method", - "duplicate-board": "Duplicate Board", - "duplicate-board-confirm": "Are you sure you want to duplicate this board?", - "org-number": "The number of organizations is: ", - "team-number": "The number of teams is: ", - "people-number": "The number of people is: ", - "swimlaneDeletePopup-title": "Delete Swimlane ?", - "swimlane-delete-pop": "All actions will be removed from the activity feed and you won't be able to recover the swimlane. There is no undo.", - "restore-all": "Restore all", - "delete-all": "Delete all", - "loading": "Loading, please wait.", - "previous_as": "last time was", - "act-a-dueAt": "modified due time to \nWhen: __timeValue__\nWhere: __card__\n previous due was __timeOldValue__", - "act-a-endAt": "modified ending time to __timeValue__ from (__timeOldValue__)", - "act-a-startAt": "modified starting time to __timeValue__ from (__timeOldValue__)", - "act-a-receivedAt": "modified received time to __timeValue__ from (__timeOldValue__)", - "a-dueAt": "modified due time to be", - "a-endAt": "modified ending time to be", - "a-startAt": "modified starting time to be", - "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", - "almostdue": "current due time %s is approaching", - "pastdue": "current due time %s is past", - "duenow": "current due time %s is today", - "act-newDue": "__list__/__card__ has 1st due reminder [__board__]", - "act-withDue": "__list__/__card__ due reminders [__board__]", - "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", - "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", - "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", - "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", - "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", - "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", - "accounts-allowUserDelete": "Allow users to self delete their account", - "hide-minicard-label-text": "Hide minicard label text", - "show-desktop-drag-handles": "Show desktop drag handles", - "assignee": "Assignee", - "cardAssigneesPopup-title": "Assignee", - "addmore-detail": "Add a more detailed description", - "show-on-card": "Show on Card", - "show-on-minicard": "Show on Minicard", - "new": "New", - "editOrgPopup-title": "Edit Organization", - "newOrgPopup-title": "New Organization", - "editTeamPopup-title": "Edit Team", - "newTeamPopup-title": "New Team", - "editUserPopup-title": "Edit User", - "newUserPopup-title": "New User", - "notifications": "Notifications", - "help": "Help", - "view-all": "View All", - "filter-by-unread": "Filter by Unread", - "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", - "remove-all-read": "Remove all read", - "allow-rename": "Allow Rename", - "allowRenamePopup-title": "Allow Rename", - "start-day-of-week": "Set day of the week start", - "monday": "Monday", - "tuesday": "Tuesday", - "wednesday": "Wednesday", - "thursday": "Thursday", - "friday": "Friday", - "saturday": "Saturday", - "sunday": "Sunday", - "status": "Status", - "swimlane": "Swimlane", - "owner": "Owner", - "last-modified-at": "Last modified at", - "last-activity": "Last activity", - "voting": "Voting", - "archived": "Archived", - "delete-linked-card-before-this-card": "You can not delete this card before first deleting linked card that has", - "delete-linked-cards-before-this-list": "You can not delete this list before first deleting linked cards that are pointing to cards in this list", - "hide-checked-items": "Hide checked items", - "hide-finished-checklist": "Hide finished checklist", - "task": "Task", - "create-task": "Create Task", - "ok": "OK", - "organizations": "Organizations", - "teams": "Teams", - "displayName": "Display Name", - "shortName": "Short Name", - "autoAddUsersWithDomainName": "Automatically add users with the domain name", - "website": "Website", - "person": "Person", - "my-cards": "My Cards", - "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", - "list": "List", - "board": "Board", - "context-separator": "/", - "myCardsViewChange-title": "My Cards View", - "myCardsViewChangePopup-title": "My Cards View", - "myCardsViewChange-choice-boards": "Boards", - "myCardsViewChange-choice-table": "Table", - "myCardsSortChange-title": "My Cards Sort", - "myCardsSortChangePopup-title": "My Cards Sort", - "myCardsSortChange-choice-board": "By Board", - "myCardsSortChange-choice-dueat": "By Due Date", - "dueCards-title": "Due Cards", - "dueCardsViewChange-title": "Due Cards View", - "dueCardsViewChangePopup-title": "Due Cards View", - "dueCardsViewChange-choice-me": "Me", - "dueCardsViewChange-choice-all": "All Users", - "dueCardsViewChange-choice-all-description": "Shows all incomplete cards with a *Due* date from boards for which the user has permission.", - "dueCards-noResults-title": "No Due Cards Found", - "dueCards-noResults-description": "You don't have any cards with due dates at the moment.", - "broken-cards": "Broken Cards", - "board-title-not-found": "Board '%s' not found.", - "swimlane-title-not-found": "Swimlane '%s' not found.", - "list-title-not-found": "List '%s' not found.", - "label-not-found": "Label '%s' not found.", - "label-color-not-found": "Label color %s not found.", - "user-username-not-found": "Username '%s' not found.", - "comment-not-found": "Card with comment containing text '%s' not found.", - "org-name-not-found": "Organization '%s' not found.", - "team-name-not-found": "Team '%s' not found.", - "globalSearch-title": "Search All Boards", - "no-cards-found": "No Cards Found", - "one-card-found": "One Card Found", - "n-cards-found": "%s Cards Found", - "n-n-of-n-cards-found": "__start__-__end__ of __total__ Cards Found", - "operator-board": "board", - "operator-board-abbrev": "b", - "operator-swimlane": "swimlane", - "operator-swimlane-abbrev": "s", - "operator-list": "list", - "operator-list-abbrev": "l", - "operator-label": "label", - "operator-label-abbrev": "#", - "operator-user": "user", - "operator-user-abbrev": "@", - "operator-member": "member", - "operator-member-abbrev": "m", - "operator-assignee": "assignee", - "operator-assignee-abbrev": "a", - "operator-creator": "creator", - "operator-status": "status", - "operator-due": "due", - "operator-created": "created", - "operator-modified": "modified", - "operator-sort": "sort", - "operator-comment": "comment", - "operator-has": "has", - "operator-limit": "limit", - "operator-debug": "debug", - "operator-org": "org", - "operator-team": "team", - "predicate-archived": "archived", - "predicate-open": "open", - "predicate-ended": "ended", - "predicate-all": "all", - "predicate-overdue": "overdue", - "predicate-week": "week", - "predicate-month": "month", - "predicate-quarter": "quarter", - "predicate-year": "year", - "predicate-due": "due", - "predicate-modified": "modified", - "predicate-created": "created", - "predicate-attachment": "attachment", - "predicate-description": "description", - "predicate-checklist": "checklist", - "predicate-start": "start", - "predicate-end": "end", - "predicate-assignee": "assignee", - "predicate-member": "member", - "predicate-public": "public", - "predicate-private": "private", - "predicate-selector": "selector", - "predicate-projection": "projection", - "operator-unknown-error": "%s is not an operator", - "operator-number-expected": "operator __operator__ expected a number, got '__value__'", - "operator-sort-invalid": "sort of '%s' is invalid", - "operator-status-invalid": "'%s' is not a valid status", - "operator-has-invalid": "%s is not a valid existence check", - "operator-limit-invalid": "%s is not a valid limit. Limit should be a positive integer.", - "operator-debug-invalid": "%s is not a valid debug predicate", - "next-page": "Next Page", - "previous-page": "Previous Page", - "heading-notes": "Notes", - "globalSearch-instructions-heading": "Search Instructions", - "globalSearch-instructions-description": "Searches can include operators to refine the search. Operators are specified by writing the operator name and value separated by a colon. For example, an operator specification of `list:Blocked` would limit the search to cards that are contained in a list named *Blocked*. If the value contains spaces or special characters it must be enclosed in quotation marks (e.g. `__operator_list__:\"To Review\"`).", - "globalSearch-instructions-operators": "Available operators:", - "globalSearch-instructions-operator-board": "`__operator_board__:<title>` - cards in boards matching the specified *<title>*", - "globalSearch-instructions-operator-list": "`__operator_list__:<title>` - cards in lists matching the specified *<title>*", - "globalSearch-instructions-operator-swimlane": "`__operator_swimlane__:<title>` - cards in swimlanes matching the specified *<title>*", - "globalSearch-instructions-operator-comment": "`__operator_comment__:<text>` - cards with a comment containing *<text>*.", - "globalSearch-instructions-operator-label": "`__operator_label__:<color>` `__operator_label__:<name>` - cards that have a label matching *<color>* or *<name>", - "globalSearch-instructions-operator-hash": "`__operator_label_abbrev__<name|color>` - shorthand for `__operator_label__:<color>` or `__operator_label__:<name>`", - "globalSearch-instructions-operator-user": "`__operator_user__:<username>` - cards where *<username>* is a *member* or *assignee*", - "globalSearch-instructions-operator-at": "`__operator_user_abbrev__username` - shorthand for `user:<username>`", - "globalSearch-instructions-operator-member": "`__operator_member__:<username>` - cards where *<username>* is a *member*", - "globalSearch-instructions-operator-assignee": "`__operator_assignee__:<username>` - cards where *<username>* is an *assignee*", - "globalSearch-instructions-operator-creator": "`__operator_creator__:<username>` - cards where *<username>* is the card's creator", - "globalSearch-instructions-operator-org": "`__operator_org__:<display name|short name>` - cards belonging to a board assigned to organization *<name>*", - "globalSearch-instructions-operator-team": "`__operator_team__:<display name|short name>` - cards belonging to a board assigned to team *<name>*", - "globalSearch-instructions-operator-due": "`__operator_due__:<n>` - cards which are due up to *<n>* days from now. `__operator_due__:__predicate_overdue__ lists all cards past their due date.", - "globalSearch-instructions-operator-created": "`__operator_created__:<n>` - cards which were created *<n>* days ago or less", - "globalSearch-instructions-operator-modified": "`__operator_modified__:<n>` - cards which were modified *<n>* days ago or less", - "globalSearch-instructions-operator-status": "`__operator_status__:<status>` - where *<status>* is one of the following:", - "globalSearch-instructions-status-archived": "`__predicate_archived__` - archived cards", - "globalSearch-instructions-status-all": "`__predicate_all__` - all archived and unarchived cards", - "globalSearch-instructions-status-ended": "`__predicate_ended__` - cards with an end date", - "globalSearch-instructions-status-public": "`__predicate_public__` - cards only in public boards", - "globalSearch-instructions-status-private": "`__predicate_private__` - cards only in private boards", - "globalSearch-instructions-operator-has": "`__operator_has__:<field>` - where *<field>* is one of `__predicate_attachment__`, `__predicate_checklist__`, `__predicate_description__`, `__predicate_start__`, `__predicate_due__`, `__predicate_end__`, `__predicate_assignee__` or `__predicate_member__`. Placing a `-` in front of *<field>* searches for the absence of a value in that field (e.g. `has:-due` searches for cards without a due date).", - "globalSearch-instructions-operator-sort": "`__operator_sort__:<sort-name>` - where *<sort-name>* is one of `__predicate_due__`, `__predicate_created__` or `__predicate_modified__`. For a descending sort, place a `-` in front of the sort name.", - "globalSearch-instructions-operator-limit": "`__operator_limit__:<n>` - where *<n>* is a positive integer expressing the number of cards to be displayed per page.", - "globalSearch-instructions-notes-1": "Multiple operators may be specified.", - "globalSearch-instructions-notes-2": "Similar operators are *OR*ed together. Cards that match any of the conditions will be returned.\n`__operator_list__:Available __operator_list__:Blocked` would return cards contained in any list named *Blocked* or *Available*.", - "globalSearch-instructions-notes-3": "Differing operators are *AND*ed together. Only cards that match all of the differing operators are returned. `__operator_list__:Available __operator_label__:red` returns only cards in the list *Available* with a *red* label.", - "globalSearch-instructions-notes-3-2": "Days can be specified as a positive or negative integer or using `__predicate_week__`, `__predicate_month__`, `__predicate_quarter__` or `__predicate_year__` for the current period.", - "globalSearch-instructions-notes-4": "Text searches are case insensitive.", - "globalSearch-instructions-notes-5": "By default archived cards are not searched.", - "link-to-search": "Link to this search", - "excel-font": "Arial", - "number": "Number", - "label-colors": "Label Colors", - "label-names": "Label Names", - "archived-at": "archived at", - "sort-cards": "Sort Cards", - "sort-is-on": "Sort is on", - "cardsSortPopup-title": "Sort Cards", - "due-date": "Due Date", - "server-error": "Server Error", - "server-error-troubleshooting": "Please submit the error generated by the server.\nFor a snap installation, run: `sudo snap logs wekan.wekan`\nFor a Docker installation, run: `sudo docker logs wekan-app`", - "title-alphabetically": "Title (Alphabetically)", - "created-at-newest-first": "Created At (Newest First)", - "created-at-oldest-first": "Created At (Oldest First)", - "links-heading": "Links", - "hide-activities-of-all-boards": "Don't show the board activities on all boards", - "now-activities-of-all-boards-are-hidden": "Now all activities of all boards are hidden", - "move-swimlane": "Move Swimlane", - "moveSwimlanePopup-title": "Move Swimlane", - "custom-field-stringtemplate": "String Template", - "custom-field-stringtemplate-format": "Format (use %{value} as placeholder)", - "custom-field-stringtemplate-separator": "Separator (use or   for a space)", - "custom-field-stringtemplate-item-placeholder": "Press enter to add more items", - "creator": "Creator", - "creator-on-minicard": "Creator on minicard", - "filesReportTitle": "Files Report", - "reports": "Reports", - "rulesReportTitle": "Rules Report", - "boardsReportTitle": "Boards Report", - "cardsReportTitle": "Cards Report", - "copy-swimlane": "Copy Swimlane", - "copySwimlanePopup-title": "Copy Swimlane", - "display-card-creator": "Display Card Creator", - "wait-spinner": "Wait Spinner", - "Bounce": "Bounce Wait Spinner", - "Cube": "Cube Wait Spinner", - "Cube-Grid": "Cube-Grid Wait Spinner", - "Dot": "Dot Wait Spinner", - "Double-Bounce": "Double Bounce Wait Spinner", - "Rotateplane": "Rotateplane Wait Spinner", - "Scaleout": "Scaleout Wait Spinner", - "Wave": "Wave Wait Spinner", - "maximize-card": "Maximize Card", - "minimize-card": "Minimize Card", - "delete-org-warning-message": "Can not delete this organization, there is at least one user that belongs to it", - "delete-team-warning-message": "Can not delete this team, there is at least one user that belongs to it", - "subject": "Subject", - "details": "Details", - "carbon-copy": "Carbon Copy (Cc:)", - "ticket": "Ticket", - "tickets": "Tickets", - "ticket-number": "Ticket Number", - "open": "Open", - "pending": "Pending", - "closed": "Closed", - "resolved": "Resolved", - "cancelled": "Cancelled", - "history": "History", - "request": "Request", - "requests": "Requests", - "help-request": "Help Request", - "editCardSortOrderPopup-title": "Change Sorting", - "cardDetailsPopup-title": "Card Details", - "add-teams": "Add teams", - "add-teams-label": "Added teams are displayed below:", - "remove-team-from-table": "Are you sure you want to remove this team from the board ?", - "confirm-btn": "Confirm", - "remove-btn": "Remove", - "filter-card-title-label": "Filter by card title", - "invite-people-success": "Invitation to register sent with success", - "invite-people-error": "Error while sending invitation to register", - "can-invite-if-same-mailDomainName": "Email domain name", - "to-create-teams-contact-admin": "To create teams, please contact the administrator.", - "Node_heap_total_heap_size": "Node heap: total heap size", - "Node_heap_total_heap_size_executable": "Node heap: total heap size executable", - "Node_heap_total_physical_size": "Node heap: total physical size", - "Node_heap_total_available_size": "Node heap: total available size", - "Node_heap_used_heap_size": "Node heap: used heap size", - "Node_heap_heap_size_limit": "Node heap: heap size limit", - "Node_heap_malloced_memory": "Node heap: malloced memory", - "Node_heap_peak_malloced_memory": "Node heap: peak malloced memory", - "Node_heap_does_zap_garbage": "Node heap: does zap garbage", - "Node_heap_number_of_native_contexts": "Node heap: number of native contexts", - "Node_heap_number_of_detached_contexts": "Node heap: number of detached contexts", - "Node_memory_usage_rss": "Node memory usage: resident set size", - "Node_memory_usage_heap_total": "Node memory usage: total size of the allocated heap", - "Node_memory_usage_heap_used": "Node memory usage: actual memory used", - "Node_memory_usage_external": "Node memory usage: external", - "add-organizations": "Add organizations", - "add-organizations-label": "Added organizations are displayed below:", - "remove-organization-from-board": "Are you sure you want to remove this organization from this board ?", - "to-create-organizations-contact-admin": "To create organizations, please contact administrator.", - "custom-legal-notice-link-url": "Custom legal notice page URL", - "acceptance_of_our_legalNotice": "By continuing, you accept our", - "legalNotice": "legal notice", - "copied": "Copied!", - "checklistActionsPopup-title": "Checklist Actions", - "moveChecklist": "Move Checklist", - "moveChecklistPopup-title": "Move Checklist", - "newlineBecomesNewChecklistItem": "Each line of text becomes one of the checklist items", - "newLineNewItem": "One line of text = one checklist item", - "newlineBecomesNewChecklistItemOriginOrder": "Each line of text becomes one of the checklist items, original order", - "originOrder": "original order", - "copyChecklist": "Copy Checklist", - "copyChecklistPopup-title": "Copy Checklist", - "card-show-lists": "Card Show Lists", - "subtaskActionsPopup-title": "Subtask Actions", - "attachmentActionsPopup-title": "Attachment Actions", - "attachment-move-storage-fs": "Move attachment to filesystem", - "attachment-move-storage-gridfs": "Move attachment to GridFS", - "attachment-move-storage-s3": "Move attachment to S3", - "attachment-move": "Move Attachment", - "move-all-attachments-to-fs": "Move all attachments to filesystem", - "move-all-attachments-to-gridfs": "Move all attachments to GridFS", - "move-all-attachments-to-s3": "Move all attachments to S3", - "move-all-attachments-of-board-to-fs": "Move all attachments of board to filesystem", - "move-all-attachments-of-board-to-gridfs": "Move all attachments of board to GridFS", - "move-all-attachments-of-board-to-s3": "Move all attachments of board to S3", - "path": "Path", - "version-name": "Version-Name", - "size": "Size", - "storage": "Storage", - "action": "Action", - "board-title": "Board Title", - "attachmentRenamePopup-title": "Rename", - "uploading": "Uploading", - "remaining_time": "Remaining time", - "speed": "Speed", - "progress": "Progress", - "password-again": "Password (again)", - "if-you-already-have-an-account": "If you already have an account", - "register": "Register", - "forgot-password": "Forgot password", - "minicardDetailsActionsPopup-title": "Card Details", - "Mongo_sessions_count": "Mongo sessions count", - "change-visibility": "Change Visibility", - "max-upload-filesize": "Max upload filesize in bytes:", - "allowed-upload-filetypes": "Allowed upload filetypes:", - "max-avatar-filesize": "Max avatar filesize in bytes:", - "allowed-avatar-filetypes": "Allowed avatar filetypes:", - "invalid-file": "If filename is invalid, upload or rename is cancelled.", - "preview-pdf-not-supported": "Your device does not support previewing PDF. Try downloading instead.", - "drag-board": "Drag board", - "translation-number": "The number of custom translation strings is:", - "delete-translation-confirm-popup": "Are you sure you want to delete this custom translation string? There is no undo.", - "newTranslationPopup-title": "New custom translation string", - "editTranslationPopup-title": "Edit custom translation string", - "settingsTranslationPopup-title": "Delete this custom translation string?", - "translation": "Translation", - "text": "Text", - "translation-text": "Translation text", - "show-subtasks-field": "Show subtasks field", - "show-week-of-year": "Show week of year (ISO 8601)", - "convert-to-markdown": "Convert to markdown", - "import-board-zip": "Add .zip file that has board JSON files, and board name subdirectories with attachments", - "collapse": "Collapse", - "uncollapse": "Uncollapse", - "hideCheckedChecklistItems": "Hide checked checklist items", - "hideAllChecklistItems": "Hide all checklist items", - "support": "Support", - "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", - "accessibility": "Accessibility", - "accessibility-page-enabled": "Accessibility page enabled", - "accessibility-info-not-added-yet": "Accessibility info has not been added yet", - "accessibility-title": "Accessibility title", - "accessibility-content": "Accessibility content", - "accounts-lockout-settings": "Brute Force Protection Settings", - "accounts-lockout-info": "These settings control how login attempts are protected against brute force attacks.", - "accounts-lockout-known-users": "Settings for known users (correct username, wrong password)", - "accounts-lockout-unknown-users": "Settings for unknown users (non-existent username)", - "accounts-lockout-failures-before": "Failures before lockout", - "accounts-lockout-period": "Lockout period (seconds)", - "accounts-lockout-failure-window": "Failure window (seconds)", - "accounts-lockout-settings-updated": "Brute force protection settings have been updated", - "accounts-lockout-locked-users": "Locked Users", - "accounts-lockout-locked-users-info": "Users currently locked out due to too many failed login attempts", - "accounts-lockout-no-locked-users": "There are currently no locked users", - "accounts-lockout-failed-attempts": "Failed Attempts", - "accounts-lockout-remaining-time": "Remaining Time", - "accounts-lockout-user-unlocked": "User has been unlocked successfully", - "accounts-lockout-confirm-unlock": "Are you sure you want to unlock this user?", - "accounts-lockout-confirm-unlock-all": "Are you sure you want to unlock all locked users?", - "accounts-lockout-show-locked-users": "Show locked users only", - "accounts-lockout-user-locked": "User is locked", - "accounts-lockout-click-to-unlock": "Click to unlock this user", - "accounts-lockout-status": "Status", - "admin-people-filter-show": "Show:", - "admin-people-filter-all": "All Users", - "admin-people-filter-locked": "Locked Users Only", - "admin-people-filter-active": "Active", - "admin-people-filter-inactive": "Not Active", - "admin-people-active-status": "Active Status", - "admin-people-user-active": "User is active - click to deactivate", - "admin-people-user-inactive": "User is inactive - click to activate", - "accounts-lockout-all-users-unlocked": "All locked users have been unlocked", - "accounts-lockout-unlock-all": "Unlock All", - "active-cron-jobs": "Active Scheduled Jobs", - "add-cron-job": "Add Scheduled Job", - "add-cron-job-placeholder": "Add Scheduled Job functionality coming soon", - "attachment-storage-configuration": "Attachment Storage Configuration", - "attachments-path": "Attachments Path", - "attachments-path-description": "Path where attachment files are stored", - "avatars-path": "Avatars Path", - "avatars-path-description": "Path where avatar files are stored", - "board-archive-failed": "Failed to schedule board archive", - "board-archive-scheduled": "Board archive scheduled successfully", - "board-backup-failed": "Failed to schedule board backup", - "board-backup-scheduled": "Board backup scheduled successfully", - "board-cleanup-failed": "Failed to schedule board cleanup", - "board-cleanup-scheduled": "Board cleanup scheduled successfully", - "board-operations": "Board Operations", - "cron-jobs": "Scheduled Jobs", - "cron-migrations": "Scheduled Migrations", - "cron-job-delete-confirm": "Are you sure you want to delete this scheduled job?", - "cron-job-delete-failed": "Failed to delete scheduled job", - "cron-job-deleted": "Scheduled job deleted successfully", - "cron-job-pause-failed": "Failed to pause scheduled job", - "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", - "filesystem-path-description": "Base path for file storage", - "gridfs-enabled": "GridFS Enabled", - "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", - "migration-pause-failed": "Failed to pause migrations", - "migration-paused": "Migrations paused successfully", - "migration-progress": "Migration Progress", - "migration-start-failed": "Failed to start migrations", - "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", - "migration-status": "Migration Status", - "migration-stop-confirm": "Are you sure you want to stop all migrations?", - "migration-stop-failed": "Failed to stop migrations", - "migration-stopped": "Migrations stopped successfully", - "mongodb-gridfs-storage": "MongoDB GridFS Storage", - "pause-all-migrations": "Pause All Migrations", - "s3-access-key": "S3 Access Key", - "s3-access-key-description": "AWS S3 access key for authentication", - "s3-access-key-placeholder": "Enter S3 access key", - "s3-bucket": "S3 Bucket", - "s3-bucket-description": "S3 bucket name for storing files", - "s3-connection-failed": "S3 connection failed", - "s3-connection-success": "S3 connection successful", - "s3-enabled": "S3 Enabled", - "s3-enabled-description": "Use AWS S3 or MinIO for file storage", - "s3-endpoint": "S3 Endpoint", - "s3-endpoint-description": "S3 endpoint URL (e.g., s3.amazonaws.com or minio.example.com)", - "s3-minio-storage": "S3/MinIO Storage", - "s3-port": "S3 Port", - "s3-port-description": "S3 endpoint port number", - "s3-region": "S3 Region", - "s3-region-description": "AWS S3 region (e.g., us-east-1)", - "s3-secret-key": "S3 Secret Key", - "s3-secret-key-description": "AWS S3 secret key for authentication", - "s3-secret-key-placeholder": "Enter S3 secret key", - "s3-secret-key-required": "S3 secret key is required", - "s3-settings-save-failed": "Failed to save S3 settings", - "s3-settings-saved": "S3 settings saved successfully", - "s3-ssl-enabled": "S3 SSL Enabled", - "s3-ssl-enabled-description": "Use SSL/TLS for S3 connections", - "save-s3-settings": "Save S3 Settings", - "schedule-board-archive": "Schedule Board Archive", - "schedule-board-backup": "Schedule Board Backup", - "schedule-board-cleanup": "Schedule Board Cleanup", - "scheduled-board-operations": "Scheduled Board Operations", - "start-all-migrations": "Start All Migrations", - "stop-all-migrations": "Stop All Migrations", - "test-s3-connection": "Test S3 Connection", - "writable-path": "Writable Path", - "writable-path-description": "Base directory path for file storage", - "add-job": "Add Job", - "attachment-migration": "Attachment Migration", - "attachment-monitoring": "Attachment Monitoring", - "attachment-settings": "Attachment Settings", - "attachment-storage-settings": "Storage Settings", - "automatic-migration": "Automatic Migration", - "back-to-settings": "Back to Settings", - "board-id": "Board ID", - "board-migration": "Board Migration", - "board-migrations": "Board Migrations", - "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", - "cleanup": "Cleanup", - "cleanup-old-jobs": "Cleanup Old Jobs", - "completed": "Completed", - "conversion-info-text": "This conversion is performed once per board and improves performance. You can continue using the board normally.", - "converting-board": "Converting Board", - "converting-board-description": "Converting board structure for improved functionality. This may take a few moments.", - "cpu-cores": "CPU Cores", - "cpu-usage": "CPU Usage", - "current-action": "Current Action", - "database-migration": "Database Migration", - "database-migration-description": "Updating database structure for improved functionality and performance. This process may take several minutes.", - "database-migrations": "Database Migrations", - "days-old": "Days Old", - "duration": "Duration", - "errors": "Errors", - "estimated-time-remaining": "Estimated time remaining", - "every-1-day": "Every 1 day", - "every-1-hour": "Every 1 hour", - "every-1-minute": "Every 1 minute", - "every-10-minutes": "Every 10 minutes", - "every-30-minutes": "Every 30 minutes", - "every-5-minutes": "Every 5 minutes", - "every-6-hours": "Every 6 hours", - "export-monitoring": "Export Monitoring", - "filesystem-attachments": "Filesystem Attachments", - "filesystem-size": "Filesystem Size", - "filesystem-storage": "Filesystem Storage", - "force-board-scan": "Force Board Scan", - "gridfs-attachments": "GridFS Attachments", - "gridfs-size": "GridFS Size", - "gridfs-storage": "GridFS", - "hide-list-on-minicard": "Hide List on Minicard", - "idle-migration": "Idle Migration", - "job-description": "Job Description", - "job-details": "Job Details", - "job-name": "Job Name", - "job-queue": "Job Queue", - "last-run": "Last Run", - "max-concurrent": "Max Concurrent", - "memory-usage": "Memory Usage", - "migrate-all-to-filesystem": "Migrate All to Filesystem", - "migrate-all-to-gridfs": "Migrate All to GridFS", - "migrate-all-to-s3": "Migrate All to S3", - "migrated-attachments": "Migrated Attachments", - "migration-batch-size": "Batch Size", - "migration-batch-size-description": "Number of attachments to process in each batch (1-100)", - "migration-cpu-threshold": "CPU Threshold (%)", - "migration-cpu-threshold-description": "Pause migration when CPU usage exceeds this percentage (10-90)", - "migration-delay-ms": "Delay (ms)", - "migration-delay-ms-description": "Delay between batches in milliseconds (100-10000)", - "migration-detector": "Migration Detector", - "migration-info-text": "Database migrations are performed once and improve system performance. The process continues in the background even if you close your browser.", - "migration-log": "Migration Log", - "migration-markers": "Migration Markers", - "migration-resume-failed": "Failed to resume migration", - "migration-resumed": "Migration resumed", - "migration-steps": "Migration Steps", - "migration-warning-text": "Please do not close your browser during migration. The process will continue in the background but may take longer to complete.", - "monitoring-export-failed": "Failed to export monitoring data", - "monitoring-refresh-failed": "Failed to refresh monitoring data", - "next": "Next", - "next-run": "Next Run", - "of": "of", - "operation-type": "Operation Type", - "overall-progress": "Overall Progress", - "page": "Page", - "pause-migration": "Pause Migration", - "previous": "Previous", - "refresh": "Refresh", - "refresh-monitoring": "Refresh Monitoring", - "remaining-attachments": "Remaining Attachments", - "resume-migration": "Resume Migration", - "run-once": "Run once", - "s3-attachments": "S3 Attachments", - "s3-size": "S3 Size", - "s3-storage": "S3", - "scanning-status": "Scanning Status", - "schedule": "Schedule", - "search-boards-or-operations": "Search boards or operations...", - "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", - "showing": "Showing", - "start-test-operation": "Start Test Operation", - "start-time": "Start Time", - "step-progress": "Step Progress", - "stop-migration": "Stop Migration", - "storage-distribution": "Storage Distribution", - "system-resources": "System Resources", - "total-attachments": "Total Attachments", - "total-operations": "Total Operations", - "total-size": "Total Size", - "unmigrated-boards": "Unmigrated Boards", - "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" -} diff --git a/imports/i18n/data/zu-ZA.i18n.json b/imports/i18n/data/zu-ZA.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/zu-ZA.i18n.json +++ b/imports/i18n/data/zu-ZA.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/data/zu.i18n.json b/imports/i18n/data/zu.i18n.json index d9ef3e7d3..eef27e1fe 100644 --- a/imports/i18n/data/zu.i18n.json +++ b/imports/i18n/data/zu.i18n.json @@ -78,18 +78,6 @@ "activity-deleteComment": "deleted comment %s", "activity-receivedDate": "edited received date to %s of %s", "activity-startDate": "edited start date to %s of %s", - "allboards.starred": "Starred", - "allboards.templates": "Templates", - "allboards.remaining": "Remaining", - "allboards.workspaces": "Workspaces", - "allboards.add-workspace": "Add Workspace", - "allboards.add-workspace-prompt": "Workspace name", - "allboards.add-subworkspace": "Add Subworkspace", - "allboards.add-subworkspace-prompt": "Subworkspace name", - "allboards.edit-workspace": "Edit workspace", - "allboards.edit-workspace-name": "Workspace name", - "allboards.edit-workspace-icon": "Workspace icon (markdown)", - "multi-selection-active": "Click checkboxes to select boards", "activity-dueDate": "edited due date to %s of %s", "activity-endDate": "edited end date to %s of %s", "add-attachment": "Add Attachment", @@ -98,7 +86,6 @@ "add-card": "Add Card", "add-card-to-top-of-list": "Add Card to Top of List", "add-card-to-bottom-of-list": "Add Card to Bottom of List", - "addListPopup-title": "Add List", "setListWidthPopup-title": "Set Widths", "set-list-width": "Set Widths", "set-list-width-value": "Set Min & Max Widths (pixels)", @@ -122,10 +109,10 @@ "add-after-list": "Add After List", "add-members": "Add Members", "added": "Added", - "addMemberPopup-title": "Add Members", + "addMemberPopup-title": "Members", "memberPopup-title": "Member Settings", "admin": "Admin", - "admin-desc": "Can view and edit cards, remove members, and change settings for the board. Can view activities.", + "admin-desc": "Can view and edit cards, remove members, and change settings for the board.", "admin-announcement": "Announcement", "admin-announcement-active": "Active System-Wide Announcement", "admin-announcement-title": "Announcement from Administrator", @@ -167,16 +154,12 @@ "board-background-image-url": "Background Image URL", "add-background-image": "Add Background Image", "remove-background-image": "Remove Background Image", - "show-at-all-boards-page": "Show at All Boards page", - "board-info-on-my-boards": "All Boards Settings", - "boardInfoOnMyBoardsPopup-title": "All Boards Settings", + "show-at-all-boards-page" : "Show at All Boards page", + "board-info-on-my-boards" : "All Boards Settings", + "boardInfoOnMyBoardsPopup-title" : "All Boards Settings", "boardInfoOnMyBoards-title": "All Boards Settings", "show-card-counter-per-list": "Show card count per list", "show-board_members-avatar": "Show Board members avatars", - "board_members": "All board members", - "card_members": "All members of current card at this board", - "board_assignees": "All assignees of all cards at this board", - "card_assignees": "All assignees of current card at this board", "board-nb-stars": "%s stars", "board-not-found": "Board not found", "board-private-info": "This board will be <strong>private</strong>.", @@ -286,8 +269,6 @@ "change-permissions": "Change permissions", "change-settings": "Change Settings", "changeAvatarPopup-title": "Change Avatar", - "delete-avatar-confirm": "Are you sure you want to delete this avatar?", - "deleteAvatarPopup-title": "Delete Avatar?", "changeLanguagePopup-title": "Change Language", "changePasswordPopup-title": "Change Password", "changePermissionsPopup-title": "Change Permissions", @@ -335,16 +316,10 @@ "comment-placeholder": "Write Comment", "comment-only": "Comment only", "comment-only-desc": "Can comment on cards only.", - "comment-assigned-only": "Only Assigned Comment", - "comment-assigned-only-desc": "Only assigned cards visible. Can comment only.", "comment-delete": "Are you sure you want to delete the comment?", "deleteCommentPopup-title": "Delete comment?", "no-comments": "No comments", - "no-comments-desc": "Can not see comments.", - "read-only": "Read Only", - "read-only-desc": "Can view cards only. Can not edit.", - "read-assigned-only": "Only Assigned Read", - "read-assigned-only-desc": "Only assigned cards visible. Can not edit.", + "no-comments-desc": "Can not see comments and activities.", "worker": "Worker", "worker-desc": "Can only move cards, assign itself to card and comment.", "computer": "Computer", @@ -352,7 +327,6 @@ "confirm-checklist-delete-popup": "Are you sure you want to delete the checklist?", "subtaskDeletePopup-title": "Delete Subtask?", "checklistDeletePopup-title": "Delete Checklist?", - "checklistItemDeletePopup-title": "Delete Checklist Item?", "copy-card-link-to-clipboard": "Copy card link to clipboard", "copy-text-to-clipboard": "Copy text to clipboard", "linkCardPopup-title": "Link Card", @@ -363,7 +337,6 @@ "copyManyCardsPopup-format": "[ {\"title\": \"First card title\", \"description\":\"First card description\"}, {\"title\":\"Second card title\",\"description\":\"Second card description\"},{\"title\":\"Last card title\",\"description\":\"Last card description\"} ]", "create": "Create", "createBoardPopup-title": "Create Board", - "createTemplateContainerPopup-title": "Add Template Container", "chooseBoardSourcePopup-title": "Import board", "createLabelPopup-title": "Create Label", "createCustomField": "Create Field", @@ -385,7 +358,7 @@ "date": "Date", "date-format": "Date Format", "date-format-yyyy-mm-dd": "YYYY-MM-DD", - "date-format-dd-mm-yyyy": "DD-MM-YYYY", + "date-format-dd-mm-yyyy": "DD-MM-YYYY", "date-format-mm-dd-yyyy": "MM-DD-YYYY", "decline": "Decline", "default-avatar": "Default avatar", @@ -557,13 +530,10 @@ "list-delete-suggest-archive": "You can move a list to Archive to remove it from the board and preserve the activity.", "lists": "Lists", "swimlanes": "Swimlanes", - "calendar": "Calendar", - "gantt": "Gantt", "log-out": "Log Out", "log-in": "Log In", "loginPopup-title": "Log In", "memberMenuPopup-title": "Member Settings", - "grey-icons": "Grey Icons", "members": "Members", "menu": "Menu", "move-selection": "Move selection", @@ -571,8 +541,6 @@ "moveCardToBottom-title": "Move to Bottom", "moveCardToTop-title": "Move to Top", "moveSelectionPopup-title": "Move selection", - "copySelectionPopup-title": "Copy selection", - "selection-color": "Selection Color", "multi-selection": "Multi-Selection", "multi-selection-label": "Set label for selection", "multi-selection-member": "Set member for selection", @@ -587,8 +555,6 @@ "no-results": "No results", "normal": "Normal", "normal-desc": "Can view and edit cards. Can't change settings.", - "normal-assigned-only": "Only Assigned Normal", - "normal-assigned-only-desc": "Only assigned cards visible. Edit as Normal user.", "not-accepted-yet": "Invitation not accepted yet", "notify-participate": "Receive updates to any cards you participate as creator or member", "notify-watch": "Receive updates to any boards, lists, or cards you’re watching", @@ -767,7 +733,7 @@ "accounts-allowEmailChange": "Allow Email Change", "accounts-allowUserNameChange": "Allow Username Change", "tableVisibilityMode-allowPrivateOnly": "Boards visibility: Allow private boards only", - "tableVisibilityMode": "Boards visibility", + "tableVisibilityMode" : "Boards visibility", "createdAt": "Created at", "modifiedAt": "Modified at", "verified": "Verified", @@ -779,7 +745,6 @@ "editCardReceivedDatePopup-title": "Change received date", "editCardEndDatePopup-title": "Change end date", "setCardColorPopup-title": "Set color", - "setSelectionColorPopup-title": "Set selection color", "setCardActionsColorPopup-title": "Choose a color", "setSwimlaneColorPopup-title": "Choose a color", "setListColorPopup-title": "Choose a color", @@ -790,8 +755,6 @@ "delete-board-confirm-popup": "All lists, cards, labels, and activities will be deleted and you won't be able to recover the board contents. There is no undo.", "boardDeletePopup-title": "Delete Board?", "delete-board": "Delete Board", - "delete-all-notifications": "Delete All Notifications", - "delete-all-notifications-confirm": "Are you sure you want to delete all notifications? This action cannot be undone.", "delete-duplicate-lists": "Delete Duplicate Lists", "delete-duplicate-lists-confirm": "Are you sure? This will delete all duplicate lists that have the same name and contain no cards.", "default-subtasks-board": "Subtasks for __board__ board", @@ -942,13 +905,6 @@ "authentication-method": "Authentication method", "authentication-type": "Authentication type", "custom-product-name": "Custom Product Name", - "custom-head-tags-enabled": "Enable custom head tags", - "custom-head-meta-tags": "Custom meta tags (HTML)", - "custom-head-link-tags": "Custom link tags (HTML)", - "custom-manifest-enabled": "Enable custom web manifest", - "custom-head-manifest-content": "Custom web manifest content (JSON)", - "custom-assetlinks-enabled": "Enable custom assetlinks.json", - "custom-assetlinks-content": "Custom assetlinks.json content (JSON)", "layout": "Layout", "hide-logo": "Hide Logo", "hide-card-counter-list": "Hide card counter list on All Boards", @@ -979,8 +935,6 @@ "a-endAt": "modified ending time to be", "a-startAt": "modified starting time to be", "a-receivedAt": "modified received time to be", - "above-selected-card": "Above selected card", - "below-selected-card": "Below selected card", "almostdue": "current due time %s is approaching", "pastdue": "current due time %s is past", "duenow": "current due time %s is today", @@ -989,7 +943,7 @@ "act-almostdue": "was reminding the current due (__timeValue__) of __card__ is approaching", "act-pastdue": "was reminding the current due (__timeValue__) of __card__ is past", "act-duenow": "was reminding the current due (__timeValue__) of __card__ is now", - "act-atUserComment": "mentioned you on card __card__: __comment__ at list __list__ at swimlane __swimlane__ at board __board__", + "act-atUserComment": "You were mentioned in [__board__] __list__/__card__", "delete-user-confirm-popup": "Are you sure you want to delete this account? There is no undo.", "delete-team-confirm-popup": "Are you sure you want to delete this team? There is no undo.", "delete-org-confirm-popup": "Are you sure you want to delete this organization? There is no undo.", @@ -1013,7 +967,6 @@ "view-all": "View All", "filter-by-unread": "Filter by Unread", "mark-all-as-read": "Mark all as read", - "mark-all-as-unread": "Mark all as unread", "remove-all-read": "Remove all read", "allow-rename": "Allow Rename", "allowRenamePopup-title": "Allow Rename", @@ -1048,10 +1001,6 @@ "person": "Person", "my-cards": "My Cards", "card": "Card", - "today": "Today", - "day": "Day", - "week": "Week", - "month": "Month", "list": "List", "board": "Board", "context-separator": "/", @@ -1341,11 +1290,6 @@ "hideAllChecklistItems": "Hide all checklist items", "support": "Support", "supportPopup-title": "Support", - "support-page-enabled": "Support page enabled", - "support-info-not-added-yet": "Support info has not been added yet", - "support-info-only-for-logged-in-users": "Support info is only for logged in users.", - "support-title": "Support title", - "support-content": "Support content", "accessibility": "Accessibility", "accessibility-page-enabled": "Accessibility page enabled", "accessibility-info-not-added-yet": "Accessibility info has not been added yet", @@ -1403,44 +1347,14 @@ "cron-job-deleted": "Scheduled job deleted successfully", "cron-job-pause-failed": "Failed to pause scheduled job", "cron-job-paused": "Scheduled job paused successfully", - "cron-job-resume-failed": "Failed to resume scheduled job", - "cron-job-resumed": "Scheduled job resumed successfully", - "cron-job-start-failed": "Failed to start scheduled job", - "cron-job-started": "Scheduled job started successfully", - "cron-migration-errors": "Migration Errors", - "cron-migration-warnings": "Migration Warnings", - "cron-no-errors": "No errors to display", - "cron-error-severity": "Severity", - "cron-error-time": "Time", - "cron-error-message": "Error Message", - "cron-error-details": "Details", - "cron-clear-errors": "Clear All Errors", - "cron-retry-failed": "Retry Failed Migrations", - "cron-resume-paused": "Resume Paused Migrations", - "cron-errors-cleared": "All errors cleared successfully", - "cron-no-failed-migrations": "No failed migrations to retry", - "cron-no-paused-migrations": "No paused migrations to resume", - "cron-migrations-resumed": "Migrations resumed successfully", - "cron-migrations-retried": "Failed migrations retried successfully", - "complete": "Complete", - "idle": "Idle", "filesystem-path-description": "Base path for file storage", "gridfs-enabled": "GridFS Enabled", "gridfs-enabled-description": "Use MongoDB GridFS for file storage", - "all-migrations": "All Migrations", - "select-migration": "Select Migration", - "start": "Start", - "pause": "Pause", - "stop": "Stop", - "migration-starting": "Starting migrations...", - "migration-pausing": "Pausing migrations...", - "migration-stopping": "Stopping migrations...", "migration-pause-failed": "Failed to pause migrations", "migration-paused": "Migrations paused successfully", "migration-progress": "Migration Progress", "migration-start-failed": "Failed to start migrations", "migration-started": "Migrations started successfully", - "migration-not-needed": "No migration needed", "migration-status": "Migration Status", "migration-stop-confirm": "Are you sure you want to stop all migrations?", "migration-stop-failed": "Failed to stop migrations", @@ -1490,73 +1404,7 @@ "back-to-settings": "Back to Settings", "board-id": "Board ID", "board-migration": "Board Migration", - "board-migrations": "Board Migrations", "card-show-lists-on-minicard": "Show Lists on Minicard", - "comprehensive-board-migration": "Comprehensive Board Migration", - "comprehensive-board-migration-description": "Performs comprehensive checks and fixes for board data integrity, including list ordering, card positions, and swimlane structure.", - "delete-duplicate-empty-lists-migration": "Delete Duplicate Empty Lists", - "delete-duplicate-empty-lists-migration-description": "Safely deletes empty duplicate lists. Only removes lists that have no cards AND have another list with the same title that contains cards.", - "lost-cards": "Lost Cards", - "lost-cards-list": "Restored Items", - "restore-lost-cards-migration": "Restore Lost Cards", - "restore-lost-cards-migration-description": "Finds and restores cards and lists with missing swimlaneId or listId. Creates a 'Lost Cards' swimlane to make all lost items visible again.", - "restore-all-archived-migration": "Restore All Archived", - "restore-all-archived-migration-description": "Restores all archived swimlanes, lists, and cards. Automatically fixes any missing swimlaneId or listId to make items visible.", - "fix-missing-lists-migration": "Fix Missing Lists", - "fix-missing-lists-migration-description": "Detects and repairs missing or corrupted lists in the board structure.", - "fix-avatar-urls-migration": "Fix Avatar URLs", - "fix-avatar-urls-migration-description": "Updates avatar URLs for board members to use the correct storage backend and fixes broken avatar references.", - "fix-all-file-urls-migration": "Fix All File URLs", - "fix-all-file-urls-migration-description": "Updates all file attachment URLs on this board to use the correct storage backend and fixes broken file references.", - "migration-needed": "Migration Needed", - "migration-complete": "Complete", - "migration-running": "Running...", - "migration-successful": "Migration completed successfully", - "migration-failed": "Migration failed", - "migrations": "Migrations", - "migrations-admin-only": "Only board administrators can run migrations", - "migrations-description": "Run data integrity checks and repairs for this board. Each migration can be executed individually.", - "no-issues-found": "No issues found", - "run-migration": "Run Migration", - "run-comprehensive-migration-confirm": "This will perform a comprehensive migration to check and fix board data integrity. This may take a few moments. Continue?", - "run-delete-duplicate-empty-lists-migration-confirm": "This will first convert any shared lists to per-swimlane lists, then delete empty lists that have a duplicate list with the same title containing cards. Only truly redundant empty lists will be removed. Continue?", - "run-restore-lost-cards-migration-confirm": "This will create a 'Lost Cards' swimlane and restore all cards and lists with missing swimlaneId or listId. This only affects non-archived items. Continue?", - "run-restore-all-archived-migration-confirm": "This will restore ALL archived swimlanes, lists, and cards, making them visible again. Any items with missing IDs will be automatically fixed. This cannot be easily undone. Continue?", - "run-fix-missing-lists-migration-confirm": "This will detect and repair missing or corrupted lists in the board structure. Continue?", - "run-fix-avatar-urls-migration-confirm": "This will update avatar URLs for board members to use the correct storage backend. Continue?", - "run-fix-all-file-urls-migration-confirm": "This will update all file attachment URLs on this board to use the correct storage backend. Continue?", - "restore-lost-cards-nothing-to-restore": "No lost swimlanes, lists, or cards to restore", - - "migration-progress-title": "Board Migration in Progress", - "migration-progress-overall": "Overall Progress", - "migration-progress-current-step": "Current Step", - "migration-progress-status": "Status", - "migration-progress-details": "Details", - "migration-progress-note": "Please wait while we migrate your board to the latest structure...", - "steps": "steps", - "view": "View", - "has-swimlanes": "Has Swimlanes", - - "step-analyze-board-structure": "Analyze Board Structure", - "step-fix-orphaned-cards": "Fix Orphaned Cards", - "step-convert-shared-lists": "Convert Shared Lists", - "step-ensure-per-swimlane-lists": "Ensure Per-Swimlane Lists", - "step-validate-migration": "Validate Migration", - "step-fix-avatar-urls": "Fix Avatar URLs", - "step-fix-attachment-urls": "Fix Attachment URLs", - "step-analyze-lists": "Analyze Lists", - "step-create-missing-lists": "Create Missing Lists", - "step-update-cards": "Update Cards", - "step-finalize": "Finalize", - "step-delete-duplicate-empty-lists": "Delete Duplicate Empty Lists", - "step-ensure-lost-cards-swimlane": "Ensure Lost Cards Swimlane", - "step-restore-lists": "Restore Lists", - "step-restore-cards": "Restore Cards", - "step-restore-swimlanes": "Restore Swimlanes", - "step-fix-missing-ids": "Fix Missing IDs", - "step-scan-users": "Checking board member avatars", - "step-scan-files": "Checking board file attachments", - "step-fix-file-urls": "Fixing file URLs", "cleanup": "Cleanup", "cleanup-old-jobs": "Cleanup Old Jobs", "completed": "Completed", @@ -1637,7 +1485,6 @@ "schedule": "Schedule", "search-boards-or-operations": "Search boards or operations...", "show-list-on-minicard": "Show List on Minicard", - "showChecklistAtMinicard": "Show Checklist at Minicard", "showing": "Showing", "start-test-operation": "Start Test Operation", "start-time": "Start Time", @@ -1650,37 +1497,7 @@ "total-size": "Total Size", "unmigrated-boards": "Unmigrated Boards", "weight": "Weight", - "cron": "Cron", - "current-step": "Current Step", - "otp": "OTP Code", - "create-account": "Create Account", - "already-account": "Already have an account? Sign in", - "available-repositories": "Available Repositories", - "repositories": "Repositories", - "repository": "Repository", - "repository-name": "Repository name", - "size-bytes": "Size (bytes)", - "last-modified": "Last Modified", - "no-repositories": "No repositories found", - "create-repository": "Create Repository", - "upload-repository": "Upload/Update Repository", - "api-endpoints": "API Endpoints", - "sign-in-to-upload": "Sign In to upload repositories", - "account-locked": "Account is temporarily locked due to too many failed login attempts. Please try again later.", - "otp-required": "OTP code required", - "invalid-credentials": "Invalid username or password", - "username-password-required": "Username and password are required", - "password-mismatch": "Passwords do not match", - "username-too-short": "Username must be at least 3 characters", - "user-exists": "User already exists", - "account-created": "Account created! You can now sign in.", - "account-creation-failed": "Failed to create account", - "login": "Login", - "confirm": "Confirm", - "error": "Error", - "file": "File", - "log": "Log", - "logout": "Logout", - "server": "Server", - "protocol": "Protocol" + "idle": "Idle", + "complete": "Complete", + "cron": "Cron" } diff --git a/imports/i18n/languages.js b/imports/i18n/languages.js index cb5c99e46..3291df95f 100644 --- a/imports/i18n/languages.js +++ b/imports/i18n/languages.js @@ -1,982 +1,824 @@ export default { - "ace": { - code: "ace", - tag: "ace", - name: "Acehnese", - load: () => import('./data/ace.i18n.json'), - rtl: false, - }, "af": { code: "af", tag: "af", name: "Afrikaans", load: () => import('./data/af.i18n.json'), - rtl: false, + rtl: "false", }, "af_ZA": { code: "af", tag: "af_ZA", name: "Afrikaans (South Africa)", load: () => import('./data/af_ZA.i18n.json'), - rtl: false, + rtl: "false", }, "en_AU": { code: "en", tag: "en_AU", name: "English (Australia)", load: () => import('./data/en_AU.i18n.json'), - rtl: false, + rtl: "false", }, "en_ZA": { code: "en", tag: "en_ZA", name: "English (South Africa)", load: () => import('./data/en_ZA.i18n.json'), - rtl: false, + rtl: "false", }, "ar-DZ": { code: "ar", tag: "ar-DZ", name: "دزيرية", load: () => import('./data/ar-DZ.i18n.json'), - rtl: true, + rtl: "true", }, "ar-EG": { code: "ar", tag: "ar-EG", name: "مَصرى", load: () => import('./data/ar-EG.i18n.json'), - rtl: true, + rtl: "true", }, "ar": { code: "ar", tag: "ar", name: "العربية", load: () => import('./data/ar.i18n.json'), - rtl: true, + rtl: "true", }, "ary": { code: "ary", tag: "ary", name: "عربي مغربي", load: () => import('./data/ary.i18n.json'), - rtl: true, + rtl: "true", }, "az-AZ": { code: "az", tag: "az-AZ", name: "Azərbaycan (Azərbaycan)", load: () => import('./data/az-AZ.i18n.json'), - rtl: false, }, "az-LA": { code: "az", tag: "az-LA", name: "Azərbaycan (Latin)", load: () => import('./data/az-LA.i18n.json'), - rtl: false, }, "az": { code: "az", tag: "az", name: "Azərbaycan", load: () => import('./data/az.i18n.json'), - rtl: false, }, "bg": { code: "bg", tag: "bg", name: "Български", load: () => import('./data/bg.i18n.json'), - rtl: false, }, "br": { code: "br", tag: "br", name: "Brezhoneg", load: () => import('./data/br.i18n.json'), - rtl: false, }, "ca": { code: "ca", tag: "ca", - name: "Català", + name: "català", load: () => import('./data/ca.i18n.json'), - rtl: false, }, "ca-ES": { code: "ca", tag: "ca-ES", - name: "Català (Espanya)", + name: "català (Espanya)", load: () => import('./data/ca_ES.i18n.json'), - rtl: false, }, "cmn": { code: "cn", tag: "cnm", name: "官話 / 官话", load: () => import('./data/cmn.i18n.json'), - rtl: false, }, "cs": { code: "cs", tag: "cs", name: "čeština", load: () => import('./data/cs.i18n.json'), - rtl: false, }, "cs-CZ": { code: "cs", tag: "cs-CZ", name: "čeština (Česká republika)", load: () => import('./data/cs-CZ.i18n.json'), - rtl: false, }, "cy-GB": { code: "cy", tag: "cy-GB", name: "Welsh (UK)", load: () => import('./data/cy-GB.i18n.json'), - rtl: false, }, "cy": { code: "cy", tag: "cy", name: "Welsh", load: () => import('./data/cy.i18n.json'), - rtl: false, }, "da": { code: "da", tag: "da", name: "Dansk", load: () => import('./data/da.i18n.json'), - rtl: false, }, "de-AT": { code: "de", tag: "de-AT", name: "Deutsch (Österreich)", load: () => import('./data/de-AT.i18n.json'), - rtl: false, }, "de-CH": { code: "de", tag: "de-CH", name: "Deutsch (Schweiz)", load: () => import('./data/de-CH.i18n.json'), - rtl: false, }, "de-DE": { code: "de", tag: "de-DE", name: "Deutsch (Deutschland)", load: () => import('./data/de_DE.i18n.json'), - rtl: false, }, "de": { code: "de", tag: "de", name: "Deutsch", load: () => import('./data/de.i18n.json'), - rtl: false, }, "el-GR": { code: "el", tag: "el-GR", name: "Ελληνικά (Ελλάδα)", load: () => import('./data/el-GR.i18n.json'), - rtl: false, }, "el": { code: "el", tag: "el", name: "Ελληνικά", load: () => import('./data/el.i18n.json'), - rtl: false, }, "en-BR": { code: "en", tag: "en-BR", name: "English (Brazil)", load: () => import('./data/en-BR.i18n.json'), - rtl: false, }, "en-DE": { code: "en", tag: "en-DE", name: "English (Germany)", load: () => import('./data/en-DE.i18n.json'), - rtl: false, }, "en-GB": { code: "en", tag: "en-GB", name: "English (UK)", load: () => import('./data/en-GB.i18n.json'), - rtl: false, }, "en-IT": { code: "en", tag: "en-IT", name: "English (Italy)", load: () => import('./data/en-IT.i18n.json'), - rtl: false, }, "en-MY": { code: "en", tag: "en-MY", name: "English (Malaysia)", load: () => import('./data/en-MY.i18n.json'), - rtl: false, }, "en-YS": { code: "en", tag: "en-YS", name: "English (Yeshivish)", load: () => import('./data/en-YS.i18n.json'), - rtl: false, }, "en": { code: "en", tag: "en", name: "English", load: () => import('./data/en.i18n.json'), - rtl: false, }, "eo": { code: "eo", tag: "eo", name: "Esperanto", load: () => import('./data/eo.i18n.json'), - rtl: false, }, "ast-ES": { code: "es", tag: "ast-ES", - name: "Español de Asturias", + name: "español de Asturias", load: () => import('./data/ast-ES.i18n.json'), - rtl: false, }, "es-AR": { code: "es", tag: "es-AR", - name: "Español de Argentina", + name: "español de Argentina", load: () => import('./data/es-AR.i18n.json'), - rtl: false, }, "es-CL": { code: "es", tag: "es-CL", - name: "Español de Chile", + name: "español de Chile", load: () => import('./data/es-CL.i18n.json'), - rtl: false, }, "es-CO": { code: "es", tag: "es-CO", - name: "Español en Colombia", + name: "español en Colombia", load: () => import('./data/es-CO.i18n.json'), - rtl: false, }, "es-LA": { code: "es", tag: "es-LA", - name: "Español de América Latina", + name: "español de América Latina", load: () => import('./data/es-LA.i18n.json'), - rtl: false, }, "es-MX": { code: "es", tag: "es-MX", - name: "Español de México", + name: "español de México", load: () => import('./data/es-MX.i18n.json'), - rtl: false, }, "es-PE": { code: "es", tag: "es-PE", - name: "Español de Perú", + name: "español de Perú", load: () => import('./data/es-PE.i18n.json'), - rtl: false, }, "es-PY": { code: "es", tag: "es-PY", - name: "Español de Paraguayo", + name: "español de Paraguayo", load: () => import('./data/es-PY.i18n.json'), - rtl: false, }, "es": { code: "es", tag: "es", - name: "Español", + name: "español", load: () => import('./data/es.i18n.json'), - rtl: false, }, "et-EE": { code: "et", tag: "et-EE", - name: "Eesti keel (Eesti)", + name: "eesti keel (Eesti)", load: () => import('./data/et-EE.i18n.json'), - rtl: false, }, "eu": { code: "eu", tag: "eu", name: "Euskara", load: () => import('./data/eu.i18n.json'), - rtl: false, }, "fa-IR": { code: "fa", tag: "fa-IR", name: "فارسی/پارسی (ایران\u200e)", load: () => import('./data/fa-IR.i18n.json'), - rtl: true, + rtl: "true", }, "fa": { code: "fa", tag: "fa", name: "فارسی", load: () => import('./data/fa.i18n.json'), - rtl: true, + rtl: "true", }, "fi": { code: "fi", tag: "fi", name: "Suomi", load: () => import('./data/fi.i18n.json'), - rtl: false, }, "fr-BE": { code: "fr", tag: "fr-BE", name: "Français (Belgique)", load: () => import('./data/fr-BE.i18n.json'), - rtl: false, }, "fr-CA": { code: "fr", tag: "fr-CA", name: "Français (Canada)", load: () => import('./data/fr-CA.i18n.json'), - rtl: false, }, "fr-CH": { code: "fr", tag: "fr-CH", name: "Français (Schweiz)", load: () => import('./data/fr-CH.i18n.json'), - rtl: false, }, "fr": { code: "fr", tag: "fr", name: "Français", load: () => import('./data/fr.i18n.json'), - rtl: false, }, "fy-NL": { code: "fy", tag: "fy-NL", name: "Westerlauwersk Frysk (Nederlân)", load: () => import('./data/fy-NL.i18n.json'), - rtl: false, }, "fy": { code: "fy", tag: "fy", name: "Westerlauwersk Frysk", load: () => import('./data/fy.i18n.json'), - rtl: false, }, "gl-ES": { code: "gl", tag: "gl-ES", name: "Galego (España)", load: () => import('./data/gl-ES.i18n.json'), - rtl: false, }, "gl": { code: "gl", tag: "gl", name: "Galego", load: () => import('./data/gl.i18n.json'), - rtl: false, }, "gu-IN": { code: "gu", tag: "gu-IN", name: "ગુજરાતી", load: () => import('./data/gu-IN.i18n.json'), - rtl: false, }, "he-IL": { code: "he", tag: "he-IL", name: "עברית (ישראל)", load: () => import('./data/he-IL.i18n.json'), - rtl: true, + rtl: "true", }, "he": { code: "he", tag: "he", name: "עברית", load: () => import('./data/he.i18n.json'), - rtl: true, + rtl: "true", }, "hi-IN": { code: "hi", tag: "hi-IN", name: "हिंदी (भारत)", load: () => import('./data/hi-IN.i18n.json'), - rtl: false, }, "hi": { code: "hi", tag: "hi", name: "हिन्दी", load: () => import('./data/hi.i18n.json'), - rtl: false, }, "hr": { code: "hr", tag: "hr", name: "Hrvatski", load: () => import('./data/hr.i18n.json'), - rtl: false, }, "hu": { code: "hu", tag: "hu", name: "Magyar", load: () => import('./data/hu.i18n.json'), - rtl: false, }, "hy": { code: "hy", tag: "hy", name: "Հայերեն", load: () => import('./data/hy.i18n.json'), - rtl: false, }, "id": { code: "id", tag: "id", name: "Bahasa Indonesia", load: () => import('./data/id.i18n.json'), - rtl: false, }, "ig": { code: "ig", tag: "ig", name: "Igbo", load: () => import('./data/ig.i18n.json'), - rtl: false, }, "it": { code: "it", tag: "it", name: "Italiano", load: () => import('./data/it.i18n.json'), - rtl: false, }, "ja": { code: "ja", tag: "ja", name: "日本語", load: () => import('./data/ja.i18n.json'), - rtl: false, }, "ja-Hira": { code: "ja", tag: "ja-Hira", name: "平仮名", load: () => import('./data/ja-HI.i18n.json'), - rtl: false, }, "ja-JP": { code: "ja", tag: "ja-JP", name: "日本語(日本)", load: () => import('./data/ja-JP.i18n.json'), - rtl: false, }, "ka": { code: "ka", tag: "ka", name: "ქართული", load: () => import('./data/ka.i18n.json'), - rtl: false, }, "km": { code: "km", tag: "km", name: "ភាសាខ្មែរ", load: () => import('./data/km.i18n.json'), - rtl: false, }, - "km-KH": { - code: "km", - tag: "km_KH", - name: "ខ្មែរ (កម្ពុជា)", - load: () => import('./data/km-KH.i18n.json'), - rtl: false, - }, "ko-KR": { code: "ko", tag: "ko-KR", name: "한국어(한국)", load: () => import('./data/ko-KR.i18n.json'), - rtl: false, }, "ko": { code: "ko", tag: "ko", name: "한국어", load: () => import('./data/ko.i18n.json'), - rtl: false, }, "lt": { code: "lt", tag: "lt", name: "Lietuvių kalba", load: () => import('./data/lt.i18n.json'), - rtl: false, }, "lv": { code: "lv", tag: "lv", - name: "Latviešu valoda", + name: "latviešu valoda", load: () => import('./data/lv.i18n.json'), - rtl: false, }, "mk": { code: "mk", tag: "mk", name: "македонски јазик", load: () => import('./data/mk.i18n.json'), - rtl: false, }, "mn": { code: "mn", tag: "mn", name: "Монгол", load: () => import('./data/mn.i18n.json'), - rtl: false, }, "ms": { code: "ms", tag: "ms", name: "بهاس ملايو", load: () => import('./data/ms.i18n.json'), - rtl: false, // Malesia on nykyään LTR (Latinalainen Rumi) }, "ms-MY": { code: "ms", tag: "ms-MY", name: "بهاس ملايو (Malaysia)", load: () => import('./data/ms-MY.i18n.json'), - rtl: false, }, "nb": { code: "nb", tag: "nb", name: "Norsk bokmål", load: () => import('./data/nb.i18n.json'), - rtl: false, }, "nl-NL": { code: "nl", tag: "nl-NL", name: "Nederlands (Nederland)", load: () => import('./data/nl-NL.i18n.json'), - rtl: false, }, "nl": { code: "nl", tag: "nl", name: "Nederlands", load: () => import('./data/nl.i18n.json'), - rtl: false, }, "oc": { code: "oc", tag: "oc", name: "Occitan", load: () => import('./data/oc.i18n.json'), - rtl: false, }, "or-IN": { code: "or", tag: "or-IN", name: "ଓଡିଆ (ଭାରତ)", load: () => import('./data/or_IN.i18n.json'), - rtl: false, }, "pa": { code: "pa", tag: "pa", name: "ਪੰਜਾਬੀ", load: () => import('./data/pa.i18n.json'), - rtl: false, }, "pl-PL": { code: "pl", tag: "pl-PL", name: "Polski (Polska)", load: () => import('./data/pl-PL.i18n.json'), - rtl: false, }, "pl": { code: "pl", tag: "pl", name: "Polski", load: () => import('./data/pl.i18n.json'), - rtl: false, }, "pt-BR": { code: "pt", tag: "pt-BR", name: "Português do Brasil", load: () => import('./data/pt-BR.i18n.json'), - rtl: false, }, "pt": { code: "pt", tag: "pt", name: "Português", load: () => import('./data/pt.i18n.json'), - rtl: false, }, "pt-PT": { code: "pt", tag: "pt-PT", name: "Português de Portugal", load: () => import('./data/pt-PT.i18n.json'), - rtl: false, }, "ro": { code: "ro", tag: "ro", name: "Română", load: () => import('./data/ro.i18n.json'), - rtl: false, }, "ro-RO": { code: "ro", tag: "ro-RO", name: "Română (România)", load: () => import('./data/ro-RO.i18n.json'), - rtl: false, }, "ru": { code: "ru", tag: "ru", name: "Русский", load: () => import('./data/ru.i18n.json'), - rtl: false, - }, - "ru-RU": { - code: "ru", - tag: "ru_RU", - name: "Русский язык (Россия)", - load: () => import('./data/ru-RU.i18n.json'), - rtl: false, }, "sk": { code: "sk", tag: "sk", name: "Slovenčina", load: () => import('./data/sk.i18n.json'), - rtl: false, }, "sl": { code: "sl", tag: "sl", - name: "Slovenščina", + name: "slovenščina", load: () => import('./data/sl.i18n.json'), - rtl: false, - }, - "sl_SI": { - code: "sl", - tag: "sl_SI", - name: "Slovenščina (slovenija)", - load: () => import('./data/sl_SI.i18n.json'), - rtl: false, }, "sr": { code: "sr", tag: "sr", name: "Српски језик", load: () => import('./data/sr.i18n.json'), - rtl: false, }, "sv": { code: "sv", tag: "sv", name: "Svenska", load: () => import('./data/sv.i18n.json'), - rtl: false, }, "sw": { code: "sw", tag: "sw", name: "Kiswahili", load: () => import('./data/sw.i18n.json'), - rtl: false, }, "ta": { code: "ta", tag: "ta", name: "தமிழ்", load: () => import('./data/ta.i18n.json'), - rtl: false, }, "te-IN": { code: "te", tag: "te_IN", name: "తెలుగు (భారతదేశం)", load: () => import('./data/te-IN.i18n.json'), - rtl: false, }, "th": { code: "th", tag: "th", name: "ไทย", load: () => import('./data/th.i18n.json'), - rtl: false, }, "tlh": { code: "tlh", tag: "tlh", - name: "TlhIngan Hol", + name: "tlhIngan Hol", load: () => import('./data/tlh.i18n.json'), - rtl: false, }, "tr": { code: "tr", tag: "tr", name: "Türkçe", load: () => import('./data/tr.i18n.json'), - rtl: false, }, "ug": { code: "ug", tag: "ug", name: "ئۇيغۇر تىلى", load: () => import('./data/ug.i18n.json'), - rtl: true, }, "uk": { code: "uk", tag: "uk", name: "українська мова", load: () => import('./data/uk.i18n.json'), - rtl: false, }, "uk-UA": { code: "uk", tag: "uk-UA", name: "Українська (Україна)", load: () => import('./data/uk-UA.i18n.json'), - rtl: false, }, "uz-AR": { code: "uz", tag: "uz-AR", - name: "O'zbek (arab)", + name: "o'zbek (arab)", load: () => import('./data/uz-AR.i18n.json'), - rtl: true, }, "uz-LA": { code: "uz", tag: "uz-LA", - name: "O'zbek (lotin)", + name: "o'zbek (lotin)", load: () => import('./data/uz-LA.i18n.json'), - rtl: false, }, "uz-UZ": { code: "uz", tag: "uz-UZ", - name: "O'zbek (O'zbekiston)", + name: "o'zbek (O'zbekiston)", load: () => import('./data/uz-UZ.i18n.json'), - rtl: false, }, "uz": { code: "uz", tag: "uz", - name: "O'zbek", + name: "o'zbek", load: () => import('./data/uz.i18n.json'), - rtl: false, }, "ve-CC": { code: "ve", tag: "ve-CC", - name: "Vèneto", + name: "vèneto", load: () => import('./data/ve-CC.i18n.json'), - rtl: false, }, "ve-PP": { code: "ve", tag: "ve-PP", - name: "Vepsän kelʹ", + name: "vepsän kelʹ", load: () => import('./data/ve-PP.i18n.json'), - rtl: false, }, "ve": { code: "ve", tag: "ve", name: "Tshivenḓa", load: () => import('./data/ve.i18n.json'), - rtl: false, }, "vi-VN": { code: "vi", tag: "vi-VN", name: "Tiếng Việt (Việt Nam)", load: () => import('./data/vi-VN.i18n.json'), - rtl: false, }, "vi": { code: "vi", tag: "vi", name: "Tiếng Việt", load: () => import('./data/vi.i18n.json'), - rtl: false, }, "vl-SS": { code: "vl", tag: "vl-SS", name: "Vlaams", load: () => import('./data/vl-SS.i18n.json'), - rtl: false, }, "vo": { code: "vo", tag: "vo", name: "Volapük", load: () => import('./data/vo.i18n.json'), - rtl: false, }, "wa-RR": { code: "wa", tag: "wa-RR", name: "Wáray-Wáray", load: () => import('./data/wa-RR.i18n.json'), - rtl: false, }, "wa": { code: "wa", tag: "wa", - name: "Walon", + name: "walon", load: () => import('./data/wa.i18n.json'), - rtl: false, }, "wo": { code: "wo", tag: "wo", name: "ولوفل", load: () => import('./data/wo.i18n.json'), - rtl: true, }, "xh": { code: "xh", tag: "xh", - name: "IsiXhosa", + name: "isiXhosa", load: () => import('./data/xh.i18n.json'), - rtl: false, }, "yi": { code: "yi", tag: "yi", name: "ייִדיש, יידיש", load: () => import('./data/yi.i18n.json'), - rtl: true, }, "yo": { code: "yo", tag: "yo", name: "Èdè Yorùbá", load: () => import('./data/yo.i18n.json'), - rtl: false, }, "zgh": { code: "zgh", tag: "zgh", name: "ⵜⴰⵎⴰⵣⵉⵖⵜ ⵜⴰⵏⴰⵡⴰⵢⵜ", load: () => import('./data/zgh.i18n.json'), - rtl: false, // Tifinagh-kirjoitusta kirjoitetaan LTR }, "yue_CN": { code: "yue", tag: "yue_CN", name: "廣東話", load: () => import('./data/yue_CN.i18n.json'), - rtl: false, }, "zh-CN": { code: "zh", tag: "zh-CN", name: "简体中文", load: () => import('./data/zh-CN.i18n.json'), - rtl: false, }, "zh-GB": { code: "zh", tag: "zh-GB", name: "简体中文 GB2312", load: () => import('./data/zh-GB.i18n.json'), - rtl: false, }, "zh-Hans": { code: "zh", tag: "zh-Hans", name: "简化字", load: () => import('./data/zh-Hans.i18n.json'), - rtl: false, }, "zh-Hant": { code: "zh", tag: "zh-Hant", name: "正體字", load: () => import('./data/zh-Hant.i18n.json'), - rtl: false, }, "zh-HK": { code: "zh", tag: "zh-HK", name: "繁体中文(香港)", load: () => import('./data/zh-HK.i18n.json'), - rtl: false, - }, - "zh-SG": { - code: "zh", - tag: "zh-SG", - name: "中文 (新加坡)", - load: () => import('./data/zh_SG.i18n.json'), - rtl: false, }, "zh-TW": { code: "zh", tag: "zh-TW", name: "繁體中文(台灣)", load: () => import('./data/zh-TW.i18n.json'), - rtl: false, }, "zu-ZA": { code: "zu", tag: "zu-ZA", - name: "IsiZulu (Ningizimu Afrika)", + name: "isiZulu (Ningizimu Afrika)", load: () => import('./data/zu-ZA.i18n.json'), - rtl: false, }, "zu": { code: "zu", tag: "zu", - name: "IsiZulu", + name: "isiZulu", load: () => import('./data/zu.i18n.json'), - rtl: false, } }; diff --git a/imports/i18n/tap.js b/imports/i18n/tap.js index 1a5e9026a..b23f83ef3 100644 --- a/imports/i18n/tap.js +++ b/imports/i18n/tap.js @@ -40,7 +40,7 @@ export const TAPi18n = { return Object.values(languages).some(({ tag }) => tag === language); }, getSupportedLanguages() { - return Object.values(languages).map(({ name, code, tag, rtl }) => ({ name, code, tag, rtl })); + return Object.values(languages).map(({ name, code, tag }) => ({ name, code, tag })); }, getLanguage() { return this.current.get(); diff --git a/imports/lib/customHeadDefaults.js b/imports/lib/customHeadDefaults.js deleted file mode 100644 index 702600a7d..000000000 --- a/imports/lib/customHeadDefaults.js +++ /dev/null @@ -1,553 +0,0 @@ -export const DEFAULT_HEAD_META = `<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=5, user-scalable=yes, viewport-fit=cover"> -<meta http-equiv="X-UA-Compatible" content="IE=edge"> -<meta name="apple-mobile-web-app-capable" content="yes"> -<meta name="apple-mobile-web-app-status-bar-style" content="black-translucent"> -<meta name="apple-mobile-web-app-title" content="Wekan"> -<meta name="application-name" content="Wekan"> -<meta name="msapplication-TileColor" content="#00aba9"> -<meta name="theme-color" content="#ffffff">`; - -export const DEFAULT_HEAD_LINKS = `<link rel="shortcut icon" type="image/x-icon" href="/favicon.ico"> -<link rel="apple-touch-icon" sizes="180x180" href="/apple-touch-icon.png"> -<link rel="icon" type="image/png" sizes="32x32" href="/favicon-32x32.png"> -<link rel="icon" type="image/png" sizes="16x16" href="/favicon-16x16.png"> -<link rel="manifest" crossorigin="use-credentials" href="/site.webmanifest"> -<link rel="mask-icon" href="/safari-pinned-tab.svg" color="#5bbad5">`; - -export const DEFAULT_SITE_MANIFEST = `{ - "name": "Wekan", - "short_name": "Wekan", - "icons": [ - { - "src": "svg-etc/wekan-logo-512.svg", - "sizes": "any", - "type": "image/svg" - }, - { - "src": "android-chrome-192x192.png", - "sizes": "192x192", - "type": "image/png" - }, - { - "src": "android-chrome-512x512.png", - "sizes": "512x512", - "type": "image/png" - }, - { - "src": "Square150x150Logo.scale-100.png", - "sizes": "150x150", - "type": "image/png" - }, - { - "src": "Square44x44Logo.scale-100.png", - "sizes": "44x44", - "type": "image/png" - }, - { - "src": "StoreLogo.scale-100.png", - "sizes": "50x50", - "type": "image/png" - }, - { - "src": "maskable_icon.png", - "sizes": "474x474", - "type": "image/png", - "purpose": "maskable" - }, - { - "src": "monochrome-icon-512x512.png", - "sizes": "512x512", - "type": "image/png", - "purpose": "monochrome" - }, - { - "src": "windows11/SmallTile.scale-100.png", - "sizes": "71x71" - }, - { - "src": "windows11/SmallTile.scale-125.png", - "sizes": "89x89" - }, - { - "src": "windows11/SmallTile.scale-150.png", - "sizes": "107x107" - }, - { - "src": "windows11/SmallTile.scale-200.png", - "sizes": "142x142" - }, - { - "src": "windows11/SmallTile.scale-400.png", - "sizes": "284x284" - }, - { - "src": "windows11/Square150x150Logo.scale-100.png", - "sizes": "150x150" - }, - { - "src": "windows11/Square150x150Logo.scale-125.png", - "sizes": "188x188" - }, - { - "src": "windows11/Square150x150Logo.scale-150.png", - "sizes": "225x225" - }, - { - "src": "windows11/Square150x150Logo.scale-200.png", - "sizes": "300x300" - }, - { - "src": "windows11/Square150x150Logo.scale-400.png", - "sizes": "600x600" - }, - { - "src": "windows11/Wide310x150Logo.scale-100.png", - "sizes": "310x150" - }, - { - "src": "windows11/Wide310x150Logo.scale-125.png", - "sizes": "388x188" - }, - { - "src": "windows11/Wide310x150Logo.scale-150.png", - "sizes": "465x225" - }, - { - "src": "windows11/Wide310x150Logo.scale-200.png", - "sizes": "620x300" - }, - { - "src": "windows11/Wide310x150Logo.scale-400.png", - "sizes": "1240x600" - }, - { - "src": "windows11/LargeTile.scale-100.png", - "sizes": "310x310" - }, - { - "src": "windows11/LargeTile.scale-125.png", - "sizes": "388x388" - }, - { - "src": "windows11/LargeTile.scale-150.png", - "sizes": "465x465" - }, - { - "src": "windows11/LargeTile.scale-200.png", - "sizes": "620x620" - }, - { - "src": "windows11/LargeTile.scale-400.png", - "sizes": "1240x1240" - }, - { - "src": "windows11/Square44x44Logo.scale-100.png", - "sizes": "44x44" - }, - { - "src": "windows11/Square44x44Logo.scale-125.png", - "sizes": "55x55" - }, - { - "src": "windows11/Square44x44Logo.scale-150.png", - "sizes": "66x66" - }, - { - "src": "windows11/Square44x44Logo.scale-200.png", - "sizes": "88x88" - }, - { - "src": "windows11/Square44x44Logo.scale-400.png", - "sizes": "176x176" - }, - { - "src": "windows11/StoreLogo.scale-100.png", - "sizes": "50x50" - }, - { - "src": "windows11/StoreLogo.scale-125.png", - "sizes": "63x63" - }, - { - "src": "windows11/StoreLogo.scale-150.png", - "sizes": "75x75" - }, - { - "src": "windows11/StoreLogo.scale-200.png", - "sizes": "100x100" - }, - { - "src": "windows11/StoreLogo.scale-400.png", - "sizes": "200x200" - }, - { - "src": "windows11/SplashScreen.scale-100.png", - "sizes": "620x300" - }, - { - "src": "windows11/SplashScreen.scale-125.png", - "sizes": "775x375" - }, - { - "src": "windows11/SplashScreen.scale-150.png", - "sizes": "930x450" - }, - { - "src": "windows11/SplashScreen.scale-200.png", - "sizes": "1240x600" - }, - { - "src": "windows11/SplashScreen.scale-400.png", - "sizes": "2480x1200" - }, - { - "src": "windows11/Square44x44Logo.targetsize-16.png", - "sizes": "16x16" - }, - { - "src": "windows11/Square44x44Logo.targetsize-20.png", - "sizes": "20x20" - }, - { - "src": "windows11/Square44x44Logo.targetsize-24.png", - "sizes": "24x24" - }, - { - "src": "windows11/Square44x44Logo.targetsize-30.png", - "sizes": "30x30" - }, - { - "src": "windows11/Square44x44Logo.targetsize-32.png", - "sizes": "32x32" - }, - { - "src": "windows11/Square44x44Logo.targetsize-36.png", - "sizes": "36x36" - }, - { - "src": "windows11/Square44x44Logo.targetsize-40.png", - "sizes": "40x40" - }, - { - "src": "windows11/Square44x44Logo.targetsize-44.png", - "sizes": "44x44" - }, - { - "src": "windows11/Square44x44Logo.targetsize-48.png", - "sizes": "48x48" - }, - { - "src": "windows11/Square44x44Logo.targetsize-60.png", - "sizes": "60x60" - }, - { - "src": "windows11/Square44x44Logo.targetsize-64.png", - "sizes": "64x64" - }, - { - "src": "windows11/Square44x44Logo.targetsize-72.png", - "sizes": "72x72" - }, - { - "src": "windows11/Square44x44Logo.targetsize-80.png", - "sizes": "80x80" - }, - { - "src": "windows11/Square44x44Logo.targetsize-96.png", - "sizes": "96x96" - }, - { - "src": "windows11/Square44x44Logo.targetsize-256.png", - "sizes": "256x256" - }, - { - "src": "windows11/Square44x44Logo.altform-unplated_targetsize-16.png", - "sizes": "16x16" - }, - { - "src": "windows11/Square44x44Logo.altform-unplated_targetsize-20.png", - "sizes": "20x20" - }, - { - "src": "windows11/Square44x44Logo.altform-unplated_targetsize-24.png", - "sizes": "24x24" - }, - { - "src": "windows11/Square44x44Logo.altform-unplated_targetsize-30.png", - "sizes": "30x30" - }, - { - "src": "windows11/Square44x44Logo.altform-unplated_targetsize-32.png", - "sizes": "32x32" - }, - { - "src": "windows11/Square44x44Logo.altform-unplated_targetsize-36.png", - "sizes": "36x36" - }, - { - "src": "windows11/Square44x44Logo.altform-unplated_targetsize-40.png", - "sizes": "40x40" - }, - { - "src": "windows11/Square44x44Logo.altform-unplated_targetsize-44.png", - "sizes": "44x44" - }, - { - "src": "windows11/Square44x44Logo.altform-unplated_targetsize-48.png", - "sizes": "48x48" - }, - { - "src": "windows11/Square44x44Logo.altform-unplated_targetsize-60.png", - "sizes": "60x60" - }, - { - "src": "windows11/Square44x44Logo.altform-unplated_targetsize-64.png", - "sizes": "64x64" - }, - { - "src": "windows11/Square44x44Logo.altform-unplated_targetsize-72.png", - "sizes": "72x72" - }, - { - "src": "windows11/Square44x44Logo.altform-unplated_targetsize-80.png", - "sizes": "80x80" - }, - { - "src": "windows11/Square44x44Logo.altform-unplated_targetsize-96.png", - "sizes": "96x96" - }, - { - "src": "windows11/Square44x44Logo.altform-unplated_targetsize-256.png", - "sizes": "256x256" - }, - { - "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-16.png", - "sizes": "16x16" - }, - { - "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-20.png", - "sizes": "20x20" - }, - { - "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-24.png", - "sizes": "24x24" - }, - { - "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-30.png", - "sizes": "30x30" - }, - { - "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-32.png", - "sizes": "32x32" - }, - { - "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-36.png", - "sizes": "36x36" - }, - { - "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-40.png", - "sizes": "40x40" - }, - { - "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-44.png", - "sizes": "44x44" - }, - { - "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-48.png", - "sizes": "48x48" - }, - { - "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-60.png", - "sizes": "60x60" - }, - { - "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-64.png", - "sizes": "64x64" - }, - { - "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-72.png", - "sizes": "72x72" - }, - { - "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-80.png", - "sizes": "80x80" - }, - { - "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-96.png", - "sizes": "96x96" - }, - { - "src": "windows11/Square44x44Logo.altform-lightunplated_targetsize-256.png", - "sizes": "256x256" - }, - { - "src": "android/android-launchericon-512-512.png", - "sizes": "512x512" - }, - { - "src": "android/android-launchericon-192-192.png", - "sizes": "192x192" - }, - { - "src": "android/android-launchericon-144-144.png", - "sizes": "144x144" - }, - { - "src": "android/android-launchericon-96-96.png", - "sizes": "96x96" - }, - { - "src": "android/android-launchericon-72-72.png", - "sizes": "72x72" - }, - { - "src": "android/android-launchericon-48-48.png", - "sizes": "48x48" - }, - { - "src": "ios/16.png", - "sizes": "16x16" - }, - { - "src": "ios/20.png", - "sizes": "20x20" - }, - { - "src": "ios/29.png", - "sizes": "29x29" - }, - { - "src": "ios/32.png", - "sizes": "32x32" - }, - { - "src": "ios/40.png", - "sizes": "40x40" - }, - { - "src": "ios/50.png", - "sizes": "50x50" - }, - { - "src": "ios/57.png", - "sizes": "57x57" - }, - { - "src": "ios/58.png", - "sizes": "58x58" - }, - { - "src": "ios/60.png", - "sizes": "60x60" - }, - { - "src": "ios/64.png", - "sizes": "64x64" - }, - { - "src": "ios/72.png", - "sizes": "72x72" - }, - { - "src": "ios/76.png", - "sizes": "76x76" - }, - { - "src": "ios/80.png", - "sizes": "80x80" - }, - { - "src": "ios/87.png", - "sizes": "87x87" - }, - { - "src": "ios/100.png", - "sizes": "100x100" - }, - { - "src": "ios/114.png", - "sizes": "114x114" - }, - { - "src": "ios/120.png", - "sizes": "120x120" - }, - { - "src": "ios/128.png", - "sizes": "128x128" - }, - { - "src": "ios/144.png", - "sizes": "144x144" - }, - { - "src": "ios/152.png", - "sizes": "152x152" - }, - { - "src": "ios/167.png", - "sizes": "167x167" - }, - { - "src": "ios/180.png", - "sizes": "180x180" - }, - { - "src": "ios/192.png", - "sizes": "192x192" - }, - { - "src": "ios/256.png", - "sizes": "256x256" - }, - { - "src": "ios/512.png", - "sizes": "512x512" - }, - { - "src": "ios/1024.png", - "sizes": "1024x1024" - } - ], - "screenshots": [ - { - "src": "screenshot1.webp", - "sizes": "1280x720", - "type": "image/webp" - }, - { - "src": "screenshot2.webp", - "sizes": "1280x720", - "type": "image/webp" - } - ], - "theme_color": "#000000", - "background_color": "#000000", - "start_url": "sign-in", - "display": "standalone", - "orientation": "any", - "categories": [ - "productivity" - ], - "description": "Open Source kanban with MIT license", - "dir": "auto", - "prefer_related_applications": false, - "display_override": [ - "standalone" - ] -} -`; - -export const DEFAULT_ASSETLINKS = `[ { - "relation": ["delegate_permission/common.handle_all_urls"], - "target": { - "namespace": "android_app", - "package_name": "team.wekan.boards.twa", - "sha256_cert_fingerprints": [ - "AA:AA:ED:7D:4C:9C:5A:A3:B5:DA:10:66:14:34:07:5D:EB:BE:96:CD:82:7B:09:46:47:13:65:29:5B:EA:96:30", - "61:41:86:5B:05:13:9B:64:5F:39:75:5A:16:C3:F2:22:25:6C:DA:74:B9:B0:8C:5F:93:B0:D2:26:65:16:1B:E6" - ] - } - } - ] -`; diff --git a/imports/lib/dateUtils.js b/imports/lib/dateUtils.js index 46df69382..884763488 100644 --- a/imports/lib/dateUtils.js +++ b/imports/lib/dateUtils.js @@ -10,13 +10,13 @@ export function formatDateTime(date) { const d = new Date(date); if (isNaN(d.getTime())) return ''; - + const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); const hours = String(d.getHours()).padStart(2, '0'); const minutes = String(d.getMinutes()).padStart(2, '0'); - + return `${year}-${month}-${day} ${hours}:${minutes}`; } @@ -28,11 +28,11 @@ export function formatDateTime(date) { export function formatDate(date) { const d = new Date(date); if (isNaN(d.getTime())) return ''; - + const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); - + return `${year}-${month}-${day}`; } @@ -43,20 +43,16 @@ export function formatDate(date) { * @param {boolean} includeTime - Whether to include time (HH:MM) * @returns {string} Formatted date string */ -export function formatDateByUserPreference( - date, - format = 'YYYY-MM-DD', - includeTime = true, -) { +export function formatDateByUserPreference(date, format = 'YYYY-MM-DD', includeTime = true) { const d = new Date(date); if (isNaN(d.getTime())) return ''; - + const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); const hours = String(d.getHours()).padStart(2, '0'); const minutes = String(d.getMinutes()).padStart(2, '0'); - + let dateString; switch (format) { case 'DD-MM-YYYY': @@ -70,11 +66,11 @@ export function formatDateByUserPreference( dateString = `${year}-${month}-${day}`; break; } - + if (includeTime) { return `${dateString} ${hours}:${minutes}`; } - + return dateString; } @@ -86,10 +82,10 @@ export function formatDateByUserPreference( export function formatTime(date) { const d = new Date(date); if (isNaN(d.getTime())) return ''; - + const hours = String(d.getHours()).padStart(2, '0'); const minutes = String(d.getMinutes()).padStart(2, '0'); - + return `${hours}:${minutes}`; } @@ -101,20 +97,20 @@ export function formatTime(date) { export function getISOWeek(date) { const d = new Date(date); if (isNaN(d.getTime())) return 0; - + // Set to nearest Thursday: current date + 4 - current day number // Make Sunday's day number 7 const target = new Date(d); const dayNr = (d.getDay() + 6) % 7; target.setDate(target.getDate() - dayNr + 3); - + // ISO week date weeks start on monday, so correct the day number const firstThursday = target.valueOf(); target.setMonth(0, 1); if (target.getDay() !== 4) { - target.setMonth(0, 1 + ((4 - target.getDay() + 7) % 7)); + target.setMonth(0, 1 + ((4 - target.getDay()) + 7) % 7); } - + return 1 + Math.ceil((firstThursday - target) / 604800000); // 604800000 = 7 * 24 * 3600 * 1000 } @@ -138,38 +134,23 @@ export function isValidDate(date) { export function isBefore(date1, date2, unit = 'millisecond') { const d1 = new Date(date1); const d2 = new Date(date2); - + if (isNaN(d1.getTime()) || isNaN(d2.getTime())) return false; - + switch (unit) { case 'year': return d1.getFullYear() < d2.getFullYear(); case 'month': - return ( - d1.getFullYear() < d2.getFullYear() || - (d1.getFullYear() === d2.getFullYear() && d1.getMonth() < d2.getMonth()) - ); + return d1.getFullYear() < d2.getFullYear() || + (d1.getFullYear() === d2.getFullYear() && d1.getMonth() < d2.getMonth()); case 'day': - return ( - d1.getFullYear() < d2.getFullYear() || - (d1.getFullYear() === d2.getFullYear() && - d1.getMonth() < d2.getMonth()) || - (d1.getFullYear() === d2.getFullYear() && - d1.getMonth() === d2.getMonth() && - d1.getDate() < d2.getDate()) - ); + return d1.getFullYear() < d2.getFullYear() || + (d1.getFullYear() === d2.getFullYear() && d1.getMonth() < d2.getMonth()) || + (d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() < d2.getDate()); case 'hour': - return ( - d1.getTime() < d2.getTime() && - Math.floor(d1.getTime() / (1000 * 60 * 60)) < - Math.floor(d2.getTime() / (1000 * 60 * 60)) - ); + return d1.getTime() < d2.getTime() && Math.floor(d1.getTime() / (1000 * 60 * 60)) < Math.floor(d2.getTime() / (1000 * 60 * 60)); case 'minute': - return ( - d1.getTime() < d2.getTime() && - Math.floor(d1.getTime() / (1000 * 60)) < - Math.floor(d2.getTime() / (1000 * 60)) - ); + return d1.getTime() < d2.getTime() && Math.floor(d1.getTime() / (1000 * 60)) < Math.floor(d2.getTime() / (1000 * 60)); default: return d1.getTime() < d2.getTime(); } @@ -196,32 +177,20 @@ export function isAfter(date1, date2, unit = 'millisecond') { export function isSame(date1, date2, unit = 'millisecond') { const d1 = new Date(date1); const d2 = new Date(date2); - + if (isNaN(d1.getTime()) || isNaN(d2.getTime())) return false; - + switch (unit) { case 'year': return d1.getFullYear() === d2.getFullYear(); case 'month': - return ( - d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() - ); + return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth(); case 'day': - return ( - d1.getFullYear() === d2.getFullYear() && - d1.getMonth() === d2.getMonth() && - d1.getDate() === d2.getDate() - ); + return d1.getFullYear() === d2.getFullYear() && d1.getMonth() === d2.getMonth() && d1.getDate() === d2.getDate(); case 'hour': - return ( - Math.floor(d1.getTime() / (1000 * 60 * 60)) === - Math.floor(d2.getTime() / (1000 * 60 * 60)) - ); + return Math.floor(d1.getTime() / (1000 * 60 * 60)) === Math.floor(d2.getTime() / (1000 * 60 * 60)); case 'minute': - return ( - Math.floor(d1.getTime() / (1000 * 60)) === - Math.floor(d2.getTime() / (1000 * 60)) - ); + return Math.floor(d1.getTime() / (1000 * 60)) === Math.floor(d2.getTime() / (1000 * 60)); default: return d1.getTime() === d2.getTime(); } @@ -237,7 +206,7 @@ export function isSame(date1, date2, unit = 'millisecond') { export function add(date, amount, unit) { const d = new Date(date); if (isNaN(d.getTime())) return new Date(); - + switch (unit) { case 'years': d.setFullYear(d.getFullYear() + amount); @@ -260,7 +229,7 @@ export function add(date, amount, unit) { default: d.setTime(d.getTime() + amount); } - + return d; } @@ -284,7 +253,7 @@ export function subtract(date, amount, unit) { export function startOf(date, unit) { const d = new Date(date); if (isNaN(d.getTime())) return new Date(); - + switch (unit) { case 'year': d.setMonth(0, 1); @@ -307,7 +276,7 @@ export function startOf(date, unit) { d.setMilliseconds(0); break; } - + return d; } @@ -320,7 +289,7 @@ export function startOf(date, unit) { export function endOf(date, unit) { const d = new Date(date); if (isNaN(d.getTime())) return new Date(); - + switch (unit) { case 'year': d.setMonth(11, 31); @@ -343,7 +312,7 @@ export function endOf(date, unit) { d.setMilliseconds(999); break; } - + return d; } @@ -356,14 +325,14 @@ export function endOf(date, unit) { export function format(date, format = 'L') { const d = new Date(date); if (isNaN(d.getTime())) return ''; - + const year = d.getFullYear(); const month = String(d.getMonth() + 1).padStart(2, '0'); const day = String(d.getDate()).padStart(2, '0'); const hours = String(d.getHours()).padStart(2, '0'); const minutes = String(d.getMinutes()).padStart(2, '0'); const seconds = String(d.getSeconds()).padStart(2, '0'); - + switch (format) { case 'L': return `${month}/${day}/${year}`; @@ -381,8 +350,6 @@ export function format(date, format = 'L') { return `${year}-${month}-${day}`; case 'YYYY-MM-DD HH:mm': return `${year}-${month}-${day} ${hours}:${minutes}`; - case 'YYYY-MM-DD HH:mm:ss': - return `${year}-${month}-${day} ${hours}:${minutes}:${seconds}`; case 'HH:mm': return `${hours}:${minutes}`; default: @@ -399,13 +366,13 @@ export function format(date, format = 'L') { */ export function parseDate(dateString, formats = [], strict = true) { if (!dateString) return null; - + // Try native Date parsing first const nativeDate = new Date(dateString); if (!isNaN(nativeDate.getTime())) { return nativeDate; } - + // Try common formats const commonFormats = [ 'YYYY-MM-DD HH:mm', @@ -417,18 +384,18 @@ export function parseDate(dateString, formats = [], strict = true) { 'DD/MM/YYYY HH:mm', 'DD/MM/YYYY', 'DD-MM-YYYY HH:mm', - 'DD-MM-YYYY', + 'DD-MM-YYYY' ]; - + const allFormats = [...formats, ...commonFormats]; - + for (const format of allFormats) { const parsed = parseWithFormat(dateString, format); if (parsed && isValidDate(parsed)) { return parsed; } } - + return null; } @@ -441,38 +408,28 @@ export function parseDate(dateString, formats = [], strict = true) { function parseWithFormat(dateString, format) { // Simple format parsing - can be extended as needed const formatMap = { - YYYY: '\\d{4}', - MM: '\\d{2}', - DD: '\\d{2}', - HH: '\\d{2}', - mm: '\\d{2}', - ss: '\\d{2}', + 'YYYY': '\\d{4}', + 'MM': '\\d{2}', + 'DD': '\\d{2}', + 'HH': '\\d{2}', + 'mm': '\\d{2}', + 'ss': '\\d{2}' }; - + let regex = format; for (const [key, value] of Object.entries(formatMap)) { regex = regex.replace(new RegExp(key, 'g'), `(${value})`); } - + const match = dateString.match(new RegExp(regex)); if (!match) return null; - + const groups = match.slice(1); - let year, - month, - day, - hour = 0, - minute = 0, - second = 0; - + let year, month, day, hour = 0, minute = 0, second = 0; + let groupIndex = 0; for (let i = 0; i < format.length; i++) { - if ( - format[i] === 'Y' && - format[i + 1] === 'Y' && - format[i + 2] === 'Y' && - format[i + 3] === 'Y' - ) { + if (format[i] === 'Y' && format[i + 1] === 'Y' && format[i + 2] === 'Y' && format[i + 3] === 'Y') { year = parseInt(groups[groupIndex++]); i += 3; } else if (format[i] === 'M' && format[i + 1] === 'M') { @@ -492,11 +449,11 @@ function parseWithFormat(dateString, format) { i += 1; } } - + if (year === undefined || month === undefined || day === undefined) { return null; } - + return new Date(year, month, day, hour, minute, second); } @@ -531,9 +488,9 @@ export function createDate(year, month, day, hour = 0, minute = 0, second = 0) { export function fromNow(date, now = new Date()) { const d = new Date(date); const n = new Date(now); - + if (isNaN(d.getTime()) || isNaN(n.getTime())) return ''; - + const diffMs = n.getTime() - d.getTime(); const diffSeconds = Math.floor(diffMs / 1000); const diffMinutes = Math.floor(diffSeconds / 60); @@ -542,17 +499,13 @@ export function fromNow(date, now = new Date()) { const diffWeeks = Math.floor(diffDays / 7); const diffMonths = Math.floor(diffDays / 30); const diffYears = Math.floor(diffDays / 365); - + if (diffSeconds < 60) return 'a few seconds ago'; - if (diffMinutes < 60) - return `${diffMinutes} minute${diffMinutes !== 1 ? 's' : ''} ago`; - if (diffHours < 24) - return `${diffHours} hour${diffHours !== 1 ? 's' : ''} ago`; + if (diffMinutes < 60) return `${diffMinutes} minute${diffMinutes !== 1 ? 's' : ''} ago`; + if (diffHours < 24) return `${diffHours} hour${diffHours !== 1 ? 's' : ''} ago`; if (diffDays < 7) return `${diffDays} day${diffDays !== 1 ? 's' : ''} ago`; - if (diffWeeks < 4) - return `${diffWeeks} week${diffWeeks !== 1 ? 's' : ''} ago`; - if (diffMonths < 12) - return `${diffMonths} month${diffMonths !== 1 ? 's' : ''} ago`; + if (diffWeeks < 4) return `${diffWeeks} week${diffWeeks !== 1 ? 's' : ''} ago`; + if (diffMonths < 12) return `${diffMonths} month${diffMonths !== 1 ? 's' : ''} ago`; return `${diffYears} year${diffYears !== 1 ? 's' : ''} ago`; } @@ -565,36 +518,36 @@ export function fromNow(date, now = new Date()) { export function calendar(date, now = new Date()) { const d = new Date(date); const n = new Date(now); - + if (isNaN(d.getTime()) || isNaN(n.getTime())) return format(d); - + const diffMs = d.getTime() - n.getTime(); const diffDays = Math.floor(diffMs / (1000 * 60 * 60 * 24)); - + if (diffDays === 0) return 'Today'; if (diffDays === 1) return 'Tomorrow'; if (diffDays === -1) return 'Yesterday'; if (diffDays > 1 && diffDays < 7) return `In ${diffDays} days`; if (diffDays < -1 && diffDays > -7) return `${Math.abs(diffDays)} days ago`; - + return format(d, 'L'); } /** * Calculate the difference between two dates in the specified unit * @param {Date|string} date1 - First date - * @param {Date|string} date2 - Second date + * @param {Date|string} date2 - Second date * @param {string} unit - Unit of measurement ('millisecond', 'second', 'minute', 'hour', 'day', 'week', 'month', 'year') * @returns {number} Difference in the specified unit */ export function diff(date1, date2, unit = 'millisecond') { const d1 = new Date(date1); const d2 = new Date(date2); - + if (isNaN(d1.getTime()) || isNaN(d2.getTime())) return 0; - + const diffMs = d1.getTime() - d2.getTime(); - + switch (unit) { case 'millisecond': return diffMs; diff --git a/imports/lib/secureDOMPurify.js b/imports/lib/secureDOMPurify.js index 4cdf0e84d..898687dad 100644 --- a/imports/lib/secureDOMPurify.js +++ b/imports/lib/secureDOMPurify.js @@ -44,7 +44,7 @@ export function getSecureDOMPurifyConfig() { } return false; } - + // Additional check for base64 encoded SVG with script tags if (src.startsWith('data:image/svg+xml;base64,')) { try { diff --git a/imports/reactiveCache.js b/imports/reactiveCache.js index ae63df672..800904ded 100644 --- a/imports/reactiveCache.js +++ b/imports/reactiveCache.js @@ -1,297 +1,290 @@ import { DataCache } from '@wekanteam/meteor-reactive-cache'; -import Settings from '../models/settings'; // Server isn't reactive, so search for the data always. -// All methods are async for Meteor 3.0 compatibility. ReactiveCacheServer = { - async getBoard(idOrFirstObjectSelector = {}, options = {}) { - const ret = await Boards.findOneAsync(idOrFirstObjectSelector, options); + getBoard(idOrFirstObjectSelector = {}, options = {}) { + const ret = Boards.findOne(idOrFirstObjectSelector, options); return ret; }, - async getBoards(selector = {}, options = {}, getQuery = false) { + getBoards(selector = {}, options = {}, getQuery = false) { let ret = Boards.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getList(idOrFirstObjectSelector = {}, options = {}) { - const ret = await Lists.findOneAsync(idOrFirstObjectSelector, options); + getList(idOrFirstObjectSelector = {}, options = {}) { + const ret = Lists.findOne(idOrFirstObjectSelector, options); return ret; }, - async getLists(selector = {}, options = {}, getQuery = false) { + getLists(selector = {}, options = {}, getQuery = false) { let ret = Lists.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getSwimlane(idOrFirstObjectSelector = {}, options = {}) { - const ret = await Swimlanes.findOneAsync(idOrFirstObjectSelector, options); + getSwimlane(idOrFirstObjectSelector = {}, options = {}) { + const ret = Swimlanes.findOne(idOrFirstObjectSelector, options); return ret; }, - async getSwimlanes(selector = {}, options = {}, getQuery = false) { + getSwimlanes(selector = {}, options = {}, getQuery = false) { let ret = Swimlanes.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getChecklist(idOrFirstObjectSelector = {}, options = {}) { - const ret = await Checklists.findOneAsync(idOrFirstObjectSelector, options); + getChecklist(idOrFirstObjectSelector = {}, options = {}) { + const ret = Checklists.findOne(idOrFirstObjectSelector, options); return ret; }, - async getChecklists(selector = {}, options = {}, getQuery = false) { + getChecklists(selector = {}, options = {}, getQuery = false) { let ret = Checklists.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getChecklistItem(idOrFirstObjectSelector = {}, options = {}) { - const ret = await ChecklistItems.findOneAsync(idOrFirstObjectSelector, options); + getChecklistItem(idOrFirstObjectSelector = {}, options = {}) { + const ret = ChecklistItems.findOne(idOrFirstObjectSelector, options); return ret; }, - async getChecklistItems(selector = {}, options = {}, getQuery = false) { + getChecklistItems(selector = {}, options = {}, getQuery = false) { let ret = ChecklistItems.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getCard(idOrFirstObjectSelector = {}, options = {}) { - const ret = await Cards.findOneAsync(idOrFirstObjectSelector, options); + getCard(idOrFirstObjectSelector = {}, options = {}) { + const ret = Cards.findOne(idOrFirstObjectSelector, options); return ret; }, - async getCards(selector = {}, options = {}, getQuery = false) { - let ret = Cards.find(selector, options); + getCards(selector = {}, options = {}, getQuery = false) { + let ret = Cards.find(selector, options, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getCardComment(idOrFirstObjectSelector = {}, options = {}) { - const ret = await CardComments.findOneAsync(idOrFirstObjectSelector, options); + getCardComment(idOrFirstObjectSelector = {}, options = {}) { + const ret = CardComments.findOne(idOrFirstObjectSelector, options); return ret; }, - async getCardComments(selector = {}, options = {}, getQuery = false) { + getCardComments(selector = {}, options = {}, getQuery = false) { let ret = CardComments.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getCardCommentReaction(idOrFirstObjectSelector = {}, options = {}) { - const ret = await CardCommentReactions.findOneAsync(idOrFirstObjectSelector, options); + getCardCommentReaction(idOrFirstObjectSelector = {}, options = {}) { + const ret = CardCommentReactions.findOne(idOrFirstObjectSelector, options); return ret; }, - async getCardCommentReactions(selector = {}, options = {}, getQuery = false) { + getCardCommentReactions(selector = {}, options = {}, getQuery = false) { let ret = CardCommentReactions.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getCustomField(idOrFirstObjectSelector = {}, options = {}) { - const ret = await CustomFields.findOneAsync(idOrFirstObjectSelector, options); + getCustomField(idOrFirstObjectSelector = {}, options = {}) { + const ret = CustomFields.findOne(idOrFirstObjectSelector, options); return ret; }, - async getCustomFields(selector = {}, options = {}, getQuery = false) { + getCustomFields(selector = {}, options = {}, getQuery = false) { let ret = CustomFields.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getAttachment(idOrFirstObjectSelector = {}, options = {}) { + getAttachment(idOrFirstObjectSelector = {}, options = {}) { // Try new structure first let ret = Attachments.findOne(idOrFirstObjectSelector, options); if (!ret && typeof idOrFirstObjectSelector === 'string') { // Fall back to old structure for single attachment lookup - ret = await Attachments.getAttachmentWithBackwardCompatibility( - idOrFirstObjectSelector, - ); + ret = Attachments.getAttachmentWithBackwardCompatibility(idOrFirstObjectSelector); } return ret; }, - async getAttachments(selector = {}, options = {}, getQuery = false) { + getAttachments(selector = {}, options = {}, getQuery = false) { // Try new structure first let ret = Attachments.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); // If no results and we have a cardId selector, try old structure if (ret.length === 0 && selector['meta.cardId']) { - ret = await Attachments.getAttachmentsWithBackwardCompatibility(selector); + ret = Attachments.getAttachmentsWithBackwardCompatibility(selector); } } return ret; }, - async getAvatar(idOrFirstObjectSelector = {}, options = {}) { + getAvatar(idOrFirstObjectSelector = {}, options = {}) { const ret = Avatars.findOne(idOrFirstObjectSelector, options); return ret; }, - async getAvatars(selector = {}, options = {}, getQuery = false) { + getAvatars(selector = {}, options = {}, getQuery = false) { let ret = Avatars.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getUser(idOrFirstObjectSelector = {}, options = {}) { - const ret = await Users.findOneAsync(idOrFirstObjectSelector, options); + getUser(idOrFirstObjectSelector = {}, options = {}) { + const ret = Users.findOne(idOrFirstObjectSelector, options); return ret; }, - async getUsers(selector = {}, options = {}, getQuery = false) { + getUsers(selector = {}, options = {}, getQuery = false) { let ret = Users.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getOrg(idOrFirstObjectSelector = {}, options = {}) { - const ret = await Org.findOneAsync(idOrFirstObjectSelector, options); + getOrg(idOrFirstObjectSelector = {}, options = {}) { + const ret = Org.findOne(idOrFirstObjectSelector, options); return ret; }, - async getOrgs(selector = {}, options = {}, getQuery = false) { + getOrgs(selector = {}, options = {}, getQuery = false) { let ret = Org.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getTeam(idOrFirstObjectSelector = {}, options = {}) { - const ret = await Team.findOneAsync(idOrFirstObjectSelector, options); + getTeam(idOrFirstObjectSelector = {}, options = {}) { + const ret = Team.findOne(idOrFirstObjectSelector, options); return ret; }, - async getTeams(selector = {}, options = {}, getQuery = false) { + getTeams(selector = {}, options = {}, getQuery = false) { let ret = Team.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getActivity(idOrFirstObjectSelector = {}, options = {}) { - const ret = await Activities.findOneAsync(idOrFirstObjectSelector, options); + getActivity(idOrFirstObjectSelector = {}, options = {}) { + const ret = Activities.findOne(idOrFirstObjectSelector, options); return ret; }, - async getActivities(selector = {}, options = {}, getQuery = false) { + getActivities(selector = {}, options = {}, getQuery = false) { let ret = Activities.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getRule(idOrFirstObjectSelector = {}, options = {}) { - const ret = await Rules.findOneAsync(idOrFirstObjectSelector, options); + getRule(idOrFirstObjectSelector = {}, options = {}) { + const ret = Rules.findOne(idOrFirstObjectSelector, options); return ret; }, - async getRules(selector = {}, options = {}, getQuery = false) { + getRules(selector = {}, options = {}, getQuery = false) { let ret = Rules.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getAction(idOrFirstObjectSelector = {}, options = {}) { - const ret = await Actions.findOneAsync(idOrFirstObjectSelector, options); + getAction(idOrFirstObjectSelector = {}, options = {}) { + const ret = Actions.findOne(idOrFirstObjectSelector, options); return ret; }, - async getActions(selector = {}, options = {}, getQuery = false) { + getActions(selector = {}, options = {}, getQuery = false) { let ret = Actions.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getTrigger(idOrFirstObjectSelector = {}, options = {}) { - const ret = await Triggers.findOneAsync(idOrFirstObjectSelector, options); + getTrigger(idOrFirstObjectSelector = {}, options = {}) { + const ret = Triggers.findOne(idOrFirstObjectSelector, options); return ret; }, - async getTriggers(selector = {}, options = {}, getQuery = false) { + getTriggers(selector = {}, options = {}, getQuery = false) { let ret = Triggers.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getImpersonatedUser(idOrFirstObjectSelector = {}, options = {}) { - const ret = await ImpersonatedUsers.findOneAsync(idOrFirstObjectSelector, options); + getImpersonatedUser(idOrFirstObjectSelector = {}, options = {}) { + const ret = ImpersonatedUsers.findOne(idOrFirstObjectSelector, options); return ret; }, - async getImpersonatedUsers(selector = {}, options = {}, getQuery = false) { + getImpersonatedUsers(selector = {}, options = {}, getQuery = false) { let ret = ImpersonatedUsers.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getIntegration(idOrFirstObjectSelector = {}, options = {}) { - const ret = await Integrations.findOneAsync(idOrFirstObjectSelector, options); + getIntegration(idOrFirstObjectSelector = {}, options = {}) { + const ret = Integrations.findOne(idOrFirstObjectSelector, options); return ret; }, - async getIntegrations(selector = {}, options = {}, getQuery = false) { + getIntegrations(selector = {}, options = {}, getQuery = false) { let ret = Integrations.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getSessionData(idOrFirstObjectSelector = {}, options = {}) { - const ret = await SessionData.findOneAsync(idOrFirstObjectSelector, options); + getSessionData(idOrFirstObjectSelector = {}, options = {}) { + const ret = SessionData.findOne(idOrFirstObjectSelector, options); return ret; }, - async getSessionDatas(selector = {}, options = {}, getQuery = false) { + getSessionDatas(selector = {}, options = {}, getQuery = false) { let ret = SessionData.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getInvitationCode(idOrFirstObjectSelector = {}, options = {}) { - const ret = await InvitationCodes.findOneAsync(idOrFirstObjectSelector, options); + getInvitationCode(idOrFirstObjectSelector = {}, options = {}) { + const ret = InvitationCodes.findOne(idOrFirstObjectSelector, options); return ret; }, - async getInvitationCodes(selector = {}, options = {}, getQuery = false) { + getInvitationCodes(selector = {}, options = {}, getQuery = false) { let ret = InvitationCodes.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; }, - async getCurrentSetting() { - const ret = await Settings.findOneAsync(); + getCurrentSetting() { + const ret = Settings.findOne(); return ret; }, - async getCurrentUser() { - const ret = await Meteor.userAsync(); + getCurrentUser() { + const ret = Meteor.user(); return ret; }, - async getTranslation(idOrFirstObjectSelector = {}, options = {}) { - const ret = await Translation.findOneAsync(idOrFirstObjectSelector, options); + getTranslation(idOrFirstObjectSelector = {}, options = {}) { + const ret = Translation.findOne(idOrFirstObjectSelector, options); return ret; }, - async getTranslations(selector = {}, options = {}, getQuery = false) { + getTranslations(selector = {}, options = {}, getQuery = false) { let ret = Translation.find(selector, options); if (getQuery !== true) { - ret = await ret.fetchAsync(); + ret = ret.fetch(); } return ret; - }, -}; + } +} // only the Client is reactive // saving the result has a big advantage if the query is big and often searched for the same data again and again // if the data is changed in the client, the data is saved to the server and depending code is reactive called again ReactiveCacheClient = { getBoard(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__board) { - this.__board = new DataCache((_idOrFirstObjectSelect) => { + this.__board = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Boards.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = Boards.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -299,9 +292,9 @@ ReactiveCacheClient = { return ret; }, getBoards(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__boards) { - this.__boards = new DataCache((_select) => { + this.__boards = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = Boards.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -314,14 +307,11 @@ ReactiveCacheClient = { return ret; }, getList(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__list) { - this.__list = new DataCache((_idOrFirstObjectSelect) => { + this.__list = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Lists.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = Lists.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -329,9 +319,9 @@ ReactiveCacheClient = { return ret; }, getLists(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__lists) { - this.__lists = new DataCache((_select) => { + this.__lists = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = Lists.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -344,14 +334,11 @@ ReactiveCacheClient = { return ret; }, getSwimlane(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__swimlane) { - this.__swimlane = new DataCache((_idOrFirstObjectSelect) => { + this.__swimlane = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Swimlanes.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = Swimlanes.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -359,9 +346,9 @@ ReactiveCacheClient = { return ret; }, getSwimlanes(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__swimlanes) { - this.__swimlanes = new DataCache((_select) => { + this.__swimlanes = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = Swimlanes.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -374,14 +361,11 @@ ReactiveCacheClient = { return ret; }, getChecklist(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__checklist) { - this.__checklist = new DataCache((_idOrFirstObjectSelect) => { + this.__checklist = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Checklists.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = Checklists.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -389,9 +373,9 @@ ReactiveCacheClient = { return ret; }, getChecklists(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__checklists) { - this.__checklists = new DataCache((_select) => { + this.__checklists = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = Checklists.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -404,26 +388,21 @@ ReactiveCacheClient = { return ret; }, getChecklistItem(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__checklistItem) { - this.__checklistItem = new DataCache((_idOrFirstObjectSelect) => { + this.__checklistItem = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = ChecklistItems.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = ChecklistItems.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } - const ret = this.__checklistItem.get( - EJSON.stringify(idOrFirstObjectSelect), - ); + const ret = this.__checklistItem.get(EJSON.stringify(idOrFirstObjectSelect)); return ret; }, getChecklistItems(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__checklistItems) { - this.__checklistItems = new DataCache((_select) => { + this.__checklistItems = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = ChecklistItems.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -436,14 +415,11 @@ ReactiveCacheClient = { return ret; }, getCard(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__card) { - this.__card = new DataCache((_idOrFirstObjectSelect) => { + this.__card = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Cards.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = Cards.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -451,9 +427,9 @@ ReactiveCacheClient = { return ret; }, getCards(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__cards) { - this.__cards = new DataCache((_select) => { + this.__cards = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = Cards.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -466,14 +442,11 @@ ReactiveCacheClient = { return ret; }, getCardComment(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__cardComment) { - this.__cardComment = new DataCache((_idOrFirstObjectSelect) => { + this.__cardComment = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = CardComments.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = CardComments.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -481,9 +454,9 @@ ReactiveCacheClient = { return ret; }, getCardComments(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__cardComments) { - this.__cardComments = new DataCache((_select) => { + this.__cardComments = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = CardComments.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -496,31 +469,23 @@ ReactiveCacheClient = { return ret; }, getCardCommentReaction(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__cardCommentReaction) { - this.__cardCommentReaction = new DataCache((_idOrFirstObjectSelect) => { + this.__cardCommentReaction = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = CardCommentReactions.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = CardCommentReactions.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } - const ret = this.__cardCommentReaction.get( - EJSON.stringify(idOrFirstObjectSelect), - ); + const ret = this.__cardCommentReaction.get(EJSON.stringify(idOrFirstObjectSelect)); return ret; }, getCardCommentReactions(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__cardCommentReactions) { - this.__cardCommentReactions = new DataCache((_select) => { + this.__cardCommentReactions = new DataCache(_select => { const __select = EJSON.parse(_select); - let _ret = CardCommentReactions.find( - __select.selector, - __select.options, - ); + let _ret = CardCommentReactions.find(__select.selector, __select.options); if (__select.getQuery !== true) { _ret = _ret.fetch(); } @@ -531,14 +496,11 @@ ReactiveCacheClient = { return ret; }, getCustomField(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__customField) { - this.__customField = new DataCache((_idOrFirstObjectSelect) => { + this.__customField = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = CustomFields.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = CustomFields.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -546,9 +508,9 @@ ReactiveCacheClient = { return ret; }, getCustomFields(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__customFields) { - this.__customFields = new DataCache((_select) => { + this.__customFields = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = CustomFields.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -561,20 +523,15 @@ ReactiveCacheClient = { return ret; }, getAttachment(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__attachment) { - this.__attachment = new DataCache((_idOrFirstObjectSelect) => { + this.__attachment = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); // Try new structure first - let _ret = Attachments.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + let _ret = Attachments.findOne(__select.idOrFirstObjectSelector, __select.options); if (!_ret && typeof __select.idOrFirstObjectSelector === 'string') { // Fall back to old structure for single attachment lookup - _ret = Attachments.getAttachmentWithBackwardCompatibility( - __select.idOrFirstObjectSelector, - ); + _ret = Attachments.getAttachmentWithBackwardCompatibility(__select.idOrFirstObjectSelector); } return _ret; }); @@ -583,9 +540,9 @@ ReactiveCacheClient = { return ret; }, getAttachments(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__attachments) { - this.__attachments = new DataCache((_select) => { + this.__attachments = new DataCache(_select => { const __select = EJSON.parse(_select); // Try new structure first let _ret = Attachments.find(__select.selector, __select.options); @@ -593,9 +550,7 @@ ReactiveCacheClient = { _ret = _ret.fetch(); // If no results and we have a cardId selector, try old structure if (_ret.length === 0 && __select.selector['meta.cardId']) { - _ret = Attachments.getAttachmentsWithBackwardCompatibility( - __select.selector, - ); + _ret = Attachments.getAttachmentsWithBackwardCompatibility(__select.selector); } } return _ret; @@ -605,14 +560,11 @@ ReactiveCacheClient = { return ret; }, getAvatar(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__avatar) { - this.__avatar = new DataCache((_idOrFirstObjectSelect) => { + this.__avatar = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Avatars.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = Avatars.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -620,9 +572,9 @@ ReactiveCacheClient = { return ret; }, getAvatars(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__avatars) { - this.__avatars = new DataCache((_select) => { + this.__avatars = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = Avatars.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -635,14 +587,11 @@ ReactiveCacheClient = { return ret; }, getUser(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__user) { - this.__user = new DataCache((_idOrFirstObjectSelect) => { + this.__user = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Users.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = Users.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -650,9 +599,9 @@ ReactiveCacheClient = { return ret; }, getUsers(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__users) { - this.__users = new DataCache((_select) => { + this.__users = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = Users.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -665,14 +614,11 @@ ReactiveCacheClient = { return ret; }, getOrg(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__org) { - this.__org = new DataCache((_idOrFirstObjectSelect) => { + this.__org = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Org.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = Org.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -680,9 +626,9 @@ ReactiveCacheClient = { return ret; }, getOrgs(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__orgs) { - this.__orgs = new DataCache((_select) => { + this.__orgs = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = Org.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -695,14 +641,11 @@ ReactiveCacheClient = { return ret; }, getTeam(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__team) { - this.__team = new DataCache((_idOrFirstObjectSelect) => { + this.__team = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Team.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = Team.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -710,9 +653,9 @@ ReactiveCacheClient = { return ret; }, getTeams(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__teams) { - this.__teams = new DataCache((_select) => { + this.__teams = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = Team.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -725,14 +668,11 @@ ReactiveCacheClient = { return ret; }, getActivity(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__activity) { - this.__activity = new DataCache((_idOrFirstObjectSelect) => { + this.__activity = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Activities.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = Activities.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -740,9 +680,9 @@ ReactiveCacheClient = { return ret; }, getActivities(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__activities) { - this.__activities = new DataCache((_select) => { + this.__activities = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = Activities.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -755,14 +695,11 @@ ReactiveCacheClient = { return ret; }, getRule(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__rule) { - this.__rule = new DataCache((_idOrFirstObjectSelect) => { + this.__rule = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Rules.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = Rules.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -770,9 +707,9 @@ ReactiveCacheClient = { return ret; }, getRules(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__rules) { - this.__rules = new DataCache((_select) => { + this.__rules = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = Rules.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -785,14 +722,11 @@ ReactiveCacheClient = { return ret; }, getAction(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__action) { - this.__action = new DataCache((_idOrFirstObjectSelect) => { + this.__action = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Actions.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = Actions.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -800,9 +734,9 @@ ReactiveCacheClient = { return ret; }, getActions(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__actions) { - this.__actions = new DataCache((_select) => { + this.__actions = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = Actions.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -815,14 +749,11 @@ ReactiveCacheClient = { return ret; }, getTrigger(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__trigger) { - this.__trigger = new DataCache((_idOrFirstObjectSelect) => { + this.__trigger = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Triggers.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = Triggers.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -830,9 +761,9 @@ ReactiveCacheClient = { return ret; }, getTriggers(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__triggers) { - this.__triggers = new DataCache((_select) => { + this.__triggers = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = Triggers.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -845,26 +776,21 @@ ReactiveCacheClient = { return ret; }, getImpersonatedUser(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__impersonatedUser) { - this.__impersonatedUser = new DataCache((_idOrFirstObjectSelect) => { + this.__impersonatedUser = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = ImpersonatedUsers.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = ImpersonatedUsers.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } - const ret = this.__impersonatedUser.get( - EJSON.stringify(idOrFirstObjectSelect), - ); + const ret = this.__impersonatedUser.get(EJSON.stringify(idOrFirstObjectSelect)); return ret; }, getImpersonatedUsers(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__impersonatedUsers) { - this.__impersonatedUsers = new DataCache((_select) => { + this.__impersonatedUsers = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = ImpersonatedUsers.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -877,14 +803,11 @@ ReactiveCacheClient = { return ret; }, getIntegration(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__integration) { - this.__integration = new DataCache((_idOrFirstObjectSelect) => { + this.__integration = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Integrations.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = Integrations.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -892,9 +815,9 @@ ReactiveCacheClient = { return ret; }, getIntegrations(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__integrations) { - this.__integrations = new DataCache((_select) => { + this.__integrations = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = Integrations.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -907,26 +830,21 @@ ReactiveCacheClient = { return ret; }, getInvitationCode(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__invitationCode) { - this.__invitationCode = new DataCache((_idOrFirstObjectSelect) => { + this.__invitationCode = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = InvitationCodes.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = InvitationCodes.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } - const ret = this.__invitationCode.get( - EJSON.stringify(idOrFirstObjectSelect), - ); + const ret = this.__invitationCode.get(EJSON.stringify(idOrFirstObjectSelect)); return ret; }, getInvitationCodes(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__invitationCodes) { - this.__invitationCodes = new DataCache((_select) => { + this.__invitationCodes = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = InvitationCodes.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -959,14 +877,11 @@ ReactiveCacheClient = { return ret; }, getTranslation(idOrFirstObjectSelector = {}, options = {}) { - const idOrFirstObjectSelect = { idOrFirstObjectSelector, options }; + const idOrFirstObjectSelect = {idOrFirstObjectSelector, options} if (!this.__translation) { - this.__translation = new DataCache((_idOrFirstObjectSelect) => { + this.__translation = new DataCache(_idOrFirstObjectSelect => { const __select = EJSON.parse(_idOrFirstObjectSelect); - const _ret = Translation.findOne( - __select.idOrFirstObjectSelector, - __select.options, - ); + const _ret = Translation.findOne(__select.idOrFirstObjectSelector, __select.options); return _ret; }); } @@ -974,9 +889,9 @@ ReactiveCacheClient = { return ret; }, getTranslations(selector = {}, options = {}, getQuery = false) { - const select = { selector, options, getQuery }; + const select = {selector, options, getQuery} if (!this.__translations) { - this.__translations = new DataCache((_select) => { + this.__translations = new DataCache(_select => { const __select = EJSON.parse(_select); let _ret = Translation.find(__select.selector, __select.options); if (__select.getQuery !== true) { @@ -987,8 +902,8 @@ ReactiveCacheClient = { } const ret = this.__translations.get(EJSON.stringify(select)); return ret; - }, -}; + } +} // global Reactive Cache class to avoid big overhead while searching for the same data often again // This class calls 2 implementation, for server and client code @@ -996,469 +911,501 @@ ReactiveCacheClient = { // having this class here has several advantages: // - The Programmer hasn't to care about in which context he call's this class // - having all queries together in 1 class to make it possible to see which queries in Wekan happens, e.g. with console.log -// -// Methods are NOT async - they return a Promise on server (from async ReactiveCacheServer) -// and synchronous data on client (from ReactiveCacheClient). -// Server callers must await; client code uses the return value directly. ReactiveCache = { getBoard(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getBoard(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getBoard(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getBoard(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getBoard(idOrFirstObjectSelector, options); } + return ret; }, getBoards(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getBoards(selector, options, getQuery); + ret = ReactiveCacheServer.getBoards(selector, options, getQuery); } else { - return ReactiveCacheClient.getBoards(selector, options, getQuery); + ret = ReactiveCacheClient.getBoards(selector, options, getQuery); } + return ret; }, getList(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getList(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getList(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getList(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getList(idOrFirstObjectSelector, options); } + return ret; }, getLists(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getLists(selector, options, getQuery); + ret = ReactiveCacheServer.getLists(selector, options, getQuery); } else { - return ReactiveCacheClient.getLists(selector, options, getQuery); + ret = ReactiveCacheClient.getLists(selector, options, getQuery); } + return ret; }, getSwimlane(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getSwimlane(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getSwimlane(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getSwimlane(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getSwimlane(idOrFirstObjectSelector, options); } + return ret; }, getSwimlanes(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getSwimlanes(selector, options, getQuery); + ret = ReactiveCacheServer.getSwimlanes(selector, options, getQuery); } else { - return ReactiveCacheClient.getSwimlanes(selector, options, getQuery); + ret = ReactiveCacheClient.getSwimlanes(selector, options, getQuery); } + return ret; }, getChecklist(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getChecklist(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getChecklist(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getChecklist(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getChecklist(idOrFirstObjectSelector, options); } + return ret; }, getChecklists(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getChecklists(selector, options, getQuery); + ret = ReactiveCacheServer.getChecklists(selector, options, getQuery); } else { - return ReactiveCacheClient.getChecklists(selector, options, getQuery); + ret = ReactiveCacheClient.getChecklists(selector, options, getQuery); } + return ret; }, getChecklistItem(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getChecklistItem( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheServer.getChecklistItem(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getChecklistItem( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheClient.getChecklistItem(idOrFirstObjectSelector, options); } + return ret; }, getChecklistItems(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getChecklistItems(selector, options, getQuery); + ret = ReactiveCacheServer.getChecklistItems(selector, options, getQuery); } else { - return ReactiveCacheClient.getChecklistItems(selector, options, getQuery); + ret = ReactiveCacheClient.getChecklistItems(selector, options, getQuery); } + return ret; }, getCard(idOrFirstObjectSelector = {}, options = {}, noCache = false) { + let ret; if (Meteor.isServer || noCache === true) { - return ReactiveCacheServer.getCard(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getCard(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getCard(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getCard(idOrFirstObjectSelector, options); } + return ret; }, getCards(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getCards(selector, options, getQuery); + ret = ReactiveCacheServer.getCards(selector, options, getQuery); } else { - return ReactiveCacheClient.getCards(selector, options, getQuery); + ret = ReactiveCacheClient.getCards(selector, options, getQuery); } + return ret; }, getCardComment(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getCardComment( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheServer.getCardComment(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getCardComment( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheClient.getCardComment(idOrFirstObjectSelector, options); } + return ret; }, getCardComments(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getCardComments(selector, options, getQuery); + ret = ReactiveCacheServer.getCardComments(selector, options, getQuery); } else { - return ReactiveCacheClient.getCardComments(selector, options, getQuery); + ret = ReactiveCacheClient.getCardComments(selector, options, getQuery); } + return ret; }, getCardCommentReaction(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getCardCommentReaction( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheServer.getCardCommentReaction(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getCardCommentReaction( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheClient.getCardCommentReaction(idOrFirstObjectSelector, options); } + return ret; }, getCardCommentReactions(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getCardCommentReactions( - selector, - options, - getQuery, - ); + ret = ReactiveCacheServer.getCardCommentReactions(selector, options, getQuery); } else { - return ReactiveCacheClient.getCardCommentReactions( - selector, - options, - getQuery, - ); + ret = ReactiveCacheClient.getCardCommentReactions(selector, options, getQuery); } + return ret; }, getCustomField(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getCustomField( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheServer.getCustomField(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getCustomField( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheClient.getCustomField(idOrFirstObjectSelector, options); } + return ret; }, getCustomFields(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getCustomFields(selector, options, getQuery); + ret = ReactiveCacheServer.getCustomFields(selector, options, getQuery); } else { - return ReactiveCacheClient.getCustomFields(selector, options, getQuery); + ret = ReactiveCacheClient.getCustomFields(selector, options, getQuery); } + return ret; }, getAttachment(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getAttachment(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getAttachment(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getAttachment(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getAttachment(idOrFirstObjectSelector, options); } + return ret; }, getAttachments(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getAttachments(selector, options, getQuery); + ret = ReactiveCacheServer.getAttachments(selector, options, getQuery); } else { - return ReactiveCacheClient.getAttachments(selector, options, getQuery); + ret = ReactiveCacheClient.getAttachments(selector, options, getQuery); } + return ret; }, getAvatar(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getAvatar(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getAvatar(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getAvatar(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getAvatar(idOrFirstObjectSelector, options); } + return ret; }, getAvatars(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getAvatars(selector, options, getQuery); + ret = ReactiveCacheServer.getAvatars(selector, options, getQuery); } else { - return ReactiveCacheClient.getAvatars(selector, options, getQuery); + ret = ReactiveCacheClient.getAvatars(selector, options, getQuery); } + return ret; }, getUser(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getUser(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getUser(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getUser(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getUser(idOrFirstObjectSelector, options); } + return ret; }, getUsers(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getUsers(selector, options, getQuery); + ret = ReactiveCacheServer.getUsers(selector, options, getQuery); } else { - return ReactiveCacheClient.getUsers(selector, options, getQuery); + ret = ReactiveCacheClient.getUsers(selector, options, getQuery); } + return ret; }, getOrg(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getOrg(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getOrg(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getOrg(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getOrg(idOrFirstObjectSelector, options); } + return ret; }, getOrgs(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getOrgs(selector, options, getQuery); + ret = ReactiveCacheServer.getOrgs(selector, options, getQuery); } else { - return ReactiveCacheClient.getOrgs(selector, options, getQuery); + ret = ReactiveCacheClient.getOrgs(selector, options, getQuery); } + return ret; }, getTeam(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getTeam(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getTeam(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getTeam(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getTeam(idOrFirstObjectSelector, options); } + return ret; }, getTeams(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getTeams(selector, options, getQuery); + ret = ReactiveCacheServer.getTeams(selector, options, getQuery); } else { - return ReactiveCacheClient.getTeams(selector, options, getQuery); + ret = ReactiveCacheClient.getTeams(selector, options, getQuery); } + return ret; }, getActivity(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getActivity(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getActivity(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getActivity(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getActivity(idOrFirstObjectSelector, options); } + return ret; }, getActivities(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getActivities(selector, options, getQuery); + ret = ReactiveCacheServer.getActivities(selector, options, getQuery); } else { - return ReactiveCacheClient.getActivities(selector, options, getQuery); + ret = ReactiveCacheClient.getActivities(selector, options, getQuery); } + return ret; }, getRule(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getRule(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getRule(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getRule(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getRule(idOrFirstObjectSelector, options); } + return ret; }, getRules(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getRules(selector, options, getQuery); + ret = ReactiveCacheServer.getRules(selector, options, getQuery); } else { - return ReactiveCacheClient.getRules(selector, options, getQuery); + ret = ReactiveCacheClient.getRules(selector, options, getQuery); } + return ret; }, getAction(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getAction(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getAction(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getAction(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getAction(idOrFirstObjectSelector, options); } + return ret; }, getActions(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getActions(selector, options, getQuery); + ret = ReactiveCacheServer.getActions(selector, options, getQuery); } else { - return ReactiveCacheClient.getActions(selector, options, getQuery); + ret = ReactiveCacheClient.getActions(selector, options, getQuery); } + return ret; }, getTrigger(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getTrigger(idOrFirstObjectSelector, options); + ret = ReactiveCacheServer.getTrigger(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getTrigger(idOrFirstObjectSelector, options); + ret = ReactiveCacheClient.getTrigger(idOrFirstObjectSelector, options); } + return ret; }, getTriggers(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getTriggers(selector, options, getQuery); + ret = ReactiveCacheServer.getTriggers(selector, options, getQuery); } else { - return ReactiveCacheClient.getTriggers(selector, options, getQuery); + ret = ReactiveCacheClient.getTriggers(selector, options, getQuery); } + return ret; }, getImpersonatedUser(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getImpersonatedUser( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheServer.getImpersonatedUser(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getImpersonatedUser( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheClient.getImpersonatedUser(idOrFirstObjectSelector, options); } + return ret; }, getImpersonatedUsers(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getImpersonatedUsers( - selector, - options, - getQuery, - ); + ret = ReactiveCacheServer.getImpersonatedUsers(selector, options, getQuery); } else { - return ReactiveCacheClient.getImpersonatedUsers( - selector, - options, - getQuery, - ); + ret = ReactiveCacheClient.getImpersonatedUsers(selector, options, getQuery); } + return ret; }, getIntegration(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getIntegration( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheServer.getIntegration(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getIntegration( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheClient.getIntegration(idOrFirstObjectSelector, options); } + return ret; }, getIntegrations(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getIntegrations(selector, options, getQuery); + ret = ReactiveCacheServer.getIntegrations(selector, options, getQuery); } else { - return ReactiveCacheClient.getIntegrations(selector, options, getQuery); + ret = ReactiveCacheClient.getIntegrations(selector, options, getQuery); } + return ret; }, getSessionData(idOrFirstObjectSelector = {}, options = {}) { // no reactive cache, otherwise global search will not work anymore - return ReactiveCacheServer.getSessionData( - idOrFirstObjectSelector, - options, - ); + let ret = ReactiveCacheServer.getSessionData(idOrFirstObjectSelector, options); + return ret; }, getSessionDatas(selector = {}, options = {}, getQuery = false) { // no reactive cache, otherwise global search will not work anymore - return ReactiveCacheServer.getSessionDatas(selector, options, getQuery); + let ret = ReactiveCacheServer.getSessionDatas(selector, options, getQuery); + return ret; }, getInvitationCode(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getInvitationCode( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheServer.getInvitationCode(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getInvitationCode( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheClient.getInvitationCode(idOrFirstObjectSelector, options); } + return ret; }, getInvitationCodes(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getInvitationCodes(selector, options, getQuery); + ret = ReactiveCacheServer.getInvitationCodes(selector, options, getQuery); } else { - return ReactiveCacheClient.getInvitationCodes(selector, options, getQuery); + ret = ReactiveCacheClient.getInvitationCodes(selector, options, getQuery); } + return ret; }, getCurrentSetting() { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getCurrentSetting(); + ret = ReactiveCacheServer.getCurrentSetting(); } else { - return ReactiveCacheClient.getCurrentSetting(); + ret = ReactiveCacheClient.getCurrentSetting(); } + return ret; }, getCurrentUser() { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getCurrentUser(); + ret = ReactiveCacheServer.getCurrentUser(); } else { - return ReactiveCacheClient.getCurrentUser(); + ret = ReactiveCacheClient.getCurrentUser(); } + return ret; }, getTranslation(idOrFirstObjectSelector = {}, options = {}) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getTranslation( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheServer.getTranslation(idOrFirstObjectSelector, options); } else { - return ReactiveCacheClient.getTranslation( - idOrFirstObjectSelector, - options, - ); + ret = ReactiveCacheClient.getTranslation(idOrFirstObjectSelector, options); } + return ret; }, getTranslations(selector = {}, options = {}, getQuery = false) { + let ret; if (Meteor.isServer) { - return ReactiveCacheServer.getTranslations(selector, options, getQuery); + ret = ReactiveCacheServer.getTranslations(selector, options, getQuery); } else { - return ReactiveCacheClient.getTranslations(selector, options, getQuery); + ret = ReactiveCacheClient.getTranslations(selector, options, getQuery); } + return ret; }, -}; +} // Server isn't reactive, so search for the data always. ReactiveMiniMongoIndexServer = { - async getSubTasksWithParentId(parentId, addSelect = {}, options = {}) { - let ret = []; + getSubTasksWithParentId(parentId, addSelect = {}, options = {}) { + let ret = [] if (parentId) { - ret = await ReactiveCache.getCards({ parentId, ...addSelect }, options); + ret = ReactiveCache.getCards( + { parentId, + ...addSelect, + }, options); } return ret; }, - async getChecklistsWithCardId(cardId, addSelect = {}, options = {}) { - let ret = []; + getChecklistsWithCardId(cardId, addSelect = {}, options = {}) { + let ret = [] if (cardId) { - ret = await ReactiveCache.getChecklists({ cardId, ...addSelect }, options); + ret = ReactiveCache.getChecklists( + { cardId, + ...addSelect, + }, options); } return ret; }, - async getChecklistItemsWithChecklistId(checklistId, addSelect = {}, options = {}) { - let ret = []; + getChecklistItemsWithChecklistId(checklistId, addSelect = {}, options = {}) { + let ret = [] if (checklistId) { - ret = await ReactiveCache.getChecklistItems( - { checklistId, ...addSelect }, - options, - ); + ret = ReactiveCache.getChecklistItems( + { checklistId, + ...addSelect, + }, options); } return ret; }, - async getCardCommentsWithCardId(cardId, addSelect = {}, options = {}) { - let ret = []; + getCardCommentsWithCardId(cardId, addSelect = {}, options = {}) { + let ret = [] if (cardId) { - ret = await ReactiveCache.getCardComments({ cardId, ...addSelect }, options); + ret = ReactiveCache.getCardComments( + { cardId, + ...addSelect, + }, options); } return ret; }, - async getActivityWithId(activityId, addSelect = {}, options = {}) { - let ret = []; + getActivityWithId(activityId, addSelect = {}, options = {}) { + let ret = [] if (activityId) { - ret = await ReactiveCache.getActivities( - { _id: activityId, ...addSelect }, - options, - ); + ret = ReactiveCache.getActivities( + { _id: activityId, + ...addSelect, + }, options); } return ret; - }, -}; + } +} // Client side little MiniMongo DB "Index" ReactiveMiniMongoIndexClient = { getSubTasksWithParentId(parentId, addSelect = {}, options = {}) { - let ret = []; + let ret = [] if (parentId) { - const select = { addSelect, options }; + const select = {addSelect, options} if (!this.__subTasksWithId) { - this.__subTasksWithId = new DataCache((_select) => { + this.__subTasksWithId = new DataCache(_select => { const __select = EJSON.parse(_select); const _subTasks = ReactiveCache.getCards( - { parentId: { $exists: true }, ...__select.addSelect }, - __select.options, - ); - const _ret = _.groupBy(_subTasks, 'parentId'); + { parentId: { $exists: true }, + ...__select.addSelect, + }, __select.options); + const _ret = _.groupBy(_subTasks, 'parentId') return _ret; }); } @@ -1470,17 +1417,17 @@ ReactiveMiniMongoIndexClient = { return ret; }, getChecklistsWithCardId(cardId, addSelect = {}, options = {}) { - let ret = []; + let ret = [] if (cardId) { - const select = { addSelect, options }; + const select = {addSelect, options} if (!this.__checklistsWithId) { - this.__checklistsWithId = new DataCache((_select) => { + this.__checklistsWithId = new DataCache(_select => { const __select = EJSON.parse(_select); const _checklists = ReactiveCache.getChecklists( - { cardId: { $exists: true }, ...__select.addSelect }, - __select.options, - ); - const _ret = _.groupBy(_checklists, 'cardId'); + { cardId: { $exists: true }, + ...__select.addSelect, + }, __select.options); + const _ret = _.groupBy(_checklists, 'cardId') return _ret; }); } @@ -1492,39 +1439,45 @@ ReactiveMiniMongoIndexClient = { return ret; }, getChecklistItemsWithChecklistId(checklistId, addSelect = {}, options = {}) { - let ret = []; + let ret = [] if (checklistId) { - const select = { addSelect, options }; + const select = {addSelect, options} if (!this.__checklistItemsWithId) { - this.__checklistItemsWithId = new DataCache((_select) => { + this.__checklistItemsWithId = new DataCache(_select => { const __select = EJSON.parse(_select); const _checklistItems = ReactiveCache.getChecklistItems( - { checklistId: { $exists: true }, ...__select.addSelect }, - __select.options, - ); - const _ret = _.groupBy(_checklistItems, 'checklistId'); + { checklistId: { $exists: true }, + ...__select.addSelect, + }, __select.options); + const _ret = _.groupBy(_checklistItems, 'checklistId') return _ret; }); } ret = this.__checklistItemsWithId.get(EJSON.stringify(select)); if (ret) { + if (Meteor.isServer) { + ret[checklistId] = ReactiveCache.getChecklistItems( + {checklistId: checklistId, + ...addSelect + }, options); + } ret = ret[checklistId] || []; } } return ret; }, getCardCommentsWithCardId(cardId, addSelect = {}, options = {}) { - let ret = []; + let ret = [] if (cardId) { - const select = { addSelect, options }; + const select = {addSelect, options} if (!this.__cardCommentsWithId) { - this.__cardCommentsWithId = new DataCache((_select) => { + this.__cardCommentsWithId = new DataCache(_select => { const __select = EJSON.parse(_select); const _cardComments = ReactiveCache.getCardComments( - { cardId: { $exists: true }, ...__select.addSelect }, - __select.options, - ); - const _ret = _.groupBy(_cardComments, 'cardId'); + { cardId: { $exists: true }, + ...__select.addSelect, + }, __select.options); + const _ret = _.groupBy(_cardComments, 'cardId') return _ret; }); } @@ -1536,17 +1489,17 @@ ReactiveMiniMongoIndexClient = { return ret; }, getActivityWithId(activityId, addSelect = {}, options = {}) { - let ret = []; + let ret = [] if (activityId) { - const select = { addSelect, options }; + const select = {addSelect, options} if (!this.__activityWithId) { - this.__activityWithId = new DataCache((_select) => { + this.__activityWithId = new DataCache(_select => { const __select = EJSON.parse(_select); const _activities = ReactiveCache.getActivities( - { _id: { $exists: true }, ...__select.addSelect }, - __select.options, - ); - const _ret = _.indexBy(_activities, '_id'); + { _id: { $exists: true }, + ...__select.addSelect, + }, __select.options); + const _ret = _.indexBy(_activities, '_id') return _ret; }); } @@ -1556,8 +1509,8 @@ ReactiveMiniMongoIndexClient = { } } return ret; - }, -}; + } +} // global Reactive MiniMongo Index Cache class to avoid big overhead while searching for the same data often again // This class calls 2 implementation, for server and client code @@ -1569,88 +1522,48 @@ ReactiveMiniMongoIndex = { getSubTasksWithParentId(parentId, addSelect = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveMiniMongoIndexServer.getSubTasksWithParentId( - parentId, - addSelect, - options, - ); + ret = ReactiveMiniMongoIndexServer.getSubTasksWithParentId(parentId, addSelect, options); } else { - ret = ReactiveMiniMongoIndexClient.getSubTasksWithParentId( - parentId, - addSelect, - options, - ); + ret = ReactiveMiniMongoIndexClient.getSubTasksWithParentId(parentId, addSelect, options); } return ret; }, getChecklistsWithCardId(cardId, addSelect = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveMiniMongoIndexServer.getChecklistsWithCardId( - cardId, - addSelect, - options, - ); + ret = ReactiveMiniMongoIndexServer.getChecklistsWithCardId(cardId, addSelect, options); } else { - ret = ReactiveMiniMongoIndexClient.getChecklistsWithCardId( - cardId, - addSelect, - options, - ); + ret = ReactiveMiniMongoIndexClient.getChecklistsWithCardId(cardId, addSelect, options); } return ret; }, getChecklistItemsWithChecklistId(checklistId, addSelect = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveMiniMongoIndexServer.getChecklistItemsWithChecklistId( - checklistId, - addSelect, - options, - ); + ret = ReactiveMiniMongoIndexServer.getChecklistItemsWithChecklistId(checklistId, addSelect, options); } else { - ret = ReactiveMiniMongoIndexClient.getChecklistItemsWithChecklistId( - checklistId, - addSelect, - options, - ); + ret = ReactiveMiniMongoIndexClient.getChecklistItemsWithChecklistId(checklistId, addSelect, options); } return ret; }, getCardCommentsWithCardId(cardId, addSelect = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveMiniMongoIndexServer.getCardCommentsWithCardId( - cardId, - addSelect, - options, - ); + ret = ReactiveMiniMongoIndexServer.getCardCommentsWithCardId(cardId, addSelect, options); } else { - ret = ReactiveMiniMongoIndexClient.getCardCommentsWithCardId( - cardId, - addSelect, - options, - ); + ret = ReactiveMiniMongoIndexClient.getCardCommentsWithCardId(cardId, addSelect, options); } return ret; }, getActivityWithId(activityId, addSelect = {}, options = {}) { let ret; if (Meteor.isServer) { - ret = ReactiveMiniMongoIndexServer.getActivityWithId( - activityId, - addSelect, - options, - ); + ret = ReactiveMiniMongoIndexServer.getActivityWithId(activityId, addSelect, options); } else { - ret = ReactiveMiniMongoIndexClient.getActivityWithId( - activityId, - addSelect, - options, - ); + ret = ReactiveMiniMongoIndexClient.getActivityWithId(activityId, addSelect, options); } return ret; - }, -}; + } +} export { ReactiveCache, ReactiveMiniMongoIndex }; diff --git a/models/accessibilitySettings.js b/models/accessibilitySettings.js index 0cc9260dc..901cca79d 100644 --- a/models/accessibilitySettings.js +++ b/models/accessibilitySettings.js @@ -47,14 +47,14 @@ AccessibilitySettings.attachSchema( AccessibilitySettings.allow({ update(userId) { - const user = Meteor.users.findOne(userId); + const user = ReactiveCache.getUser(userId); return user && user.isAdmin; }, }); if (Meteor.isServer) { - Meteor.startup(async () => { - await AccessibilitySettings._collection.createIndexAsync({ modifiedAt: -1 }); + Meteor.startup(() => { + AccessibilitySettings._collection.createIndex({ modifiedAt: -1 }); const accessibilitySetting = AccessibilitySettings.findOne({}); if (!accessibilitySetting) { AccessibilitySettings.insert({ enabled: false, sort: 0 }); diff --git a/models/accountSettings.js b/models/accountSettings.js index 9030e56a9..0ab9d11a8 100644 --- a/models/accountSettings.js +++ b/models/accountSettings.js @@ -46,14 +46,14 @@ AccountSettings.attachSchema( AccountSettings.allow({ update(userId) { - const user = Meteor.users.findOne(userId); + const user = ReactiveCache.getUser(userId); return user && user.isAdmin; }, }); if (Meteor.isServer) { - Meteor.startup(async () => { - await AccountSettings._collection.createIndexAsync({ modifiedAt: -1 }); + Meteor.startup(() => { + AccountSettings._collection.createIndex({ modifiedAt: -1 }); AccountSettings.upsert( { _id: 'accounts-allowEmailChange' }, { diff --git a/models/actions.js b/models/actions.js index 09dd501d5..cb79181e6 100644 --- a/models/actions.js +++ b/models/actions.js @@ -5,13 +5,13 @@ Actions = new Mongo.Collection('actions'); Actions.allow({ insert(userId, doc) { - return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId)); }, update(userId, doc) { - return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId)); }, remove(userId, doc) { - return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId)); }, }); @@ -32,8 +32,8 @@ Actions.helpers({ }); if (Meteor.isServer) { - Meteor.startup(async () => { - await Actions._collection.createIndexAsync({ modifiedAt: -1 }); + Meteor.startup(() => { + Actions._collection.createIndex({ modifiedAt: -1 }); }); } diff --git a/models/activities.js b/models/activities.js index d27edcade..943fb9306 100644 --- a/models/activities.js +++ b/models/activities.js @@ -74,29 +74,29 @@ Activities.before.insert((userId, doc) => { doc.modifiedAt = doc.createdAt; }); -if (Meteor.isServer) { - Activities.after.insert((userId, doc) => { - const activity = Activities._transform(doc); - RulesHelper.executeRules(activity); - }); +Activities.after.insert((userId, doc) => { + const activity = Activities._transform(doc); + RulesHelper.executeRules(activity); +}); +if (Meteor.isServer) { // For efficiency create indexes on the date of creation, and on the date of // creation in conjunction with the card or board id, as corresponding views // are largely used in the App. See #524. - Meteor.startup(async () => { - await Activities._collection.createIndexAsync({ createdAt: -1 }); - await Activities._collection.createIndexAsync({ modifiedAt: -1 }); - await Activities._collection.createIndexAsync({ cardId: 1, createdAt: -1 }); - await Activities._collection.createIndexAsync({ boardId: 1, createdAt: -1 }); - await Activities._collection.createIndexAsync( + Meteor.startup(() => { + Activities._collection.createIndex({ createdAt: -1 }); + Activities._collection.createIndex({ modifiedAt: -1 }); + Activities._collection.createIndex({ cardId: 1, createdAt: -1 }); + Activities._collection.createIndex({ boardId: 1, createdAt: -1 }); + Activities._collection.createIndex( { commentId: 1 }, { partialFilterExpression: { commentId: { $exists: true } } }, ); - await Activities._collection.createIndexAsync( + Activities._collection.createIndex( { attachmentId: 1 }, { partialFilterExpression: { attachmentId: { $exists: true } } }, ); - await Activities._collection.createIndexAsync( + Activities._collection.createIndex( { customFieldId: 1 }, { partialFilterExpression: { customFieldId: { $exists: true } } }, ); @@ -105,12 +105,12 @@ if (Meteor.isServer) { //Activities._collection.dropIndex({ labelId: 1 }, { partialFilterExpression: { labelId: { $exists: true } } }); }); - Activities.after.insert(async (userId, doc) => { + Activities.after.insert((userId, doc) => { const activity = Activities._transform(doc); let participants = []; let watchers = []; let title = 'act-activity-notify'; - const board = await ReactiveCache.getBoard(activity.boardId); + const board = ReactiveCache.getBoard(activity.boardId); const description = `act-${activity.activityType}`; const params = { activityId: activity._id, @@ -118,7 +118,7 @@ if (Meteor.isServer) { if (activity.userId) { // No need send notification to user of activity // participants = _.union(participants, [activity.userId]); - const user = await activity.user(); + const user = activity.user(); if (user) { if (user.getName()) { params.user = user.getName(); @@ -146,7 +146,7 @@ if (Meteor.isServer) { params.boardId = activity.boardId; } if (activity.oldBoardId) { - const oldBoard = await activity.oldBoard(); + const oldBoard = activity.oldBoard(); if (oldBoard) { watchers = _.union(watchers, oldBoard.watchers || []); params.oldBoard = oldBoard.title; @@ -155,10 +155,10 @@ if (Meteor.isServer) { } if (activity.memberId) { participants = _.union(participants, [activity.memberId]); - params.member = (await activity.member()).getName(); + params.member = activity.member().getName(); } if (activity.listId) { - const list = await activity.list(); + const list = activity.list(); if (list) { if (list.watchers !== undefined) { watchers = _.union(watchers, list.watchers || []); @@ -168,7 +168,7 @@ if (Meteor.isServer) { } } if (activity.oldListId) { - const oldList = await activity.oldList(); + const oldList = activity.oldList(); if (oldList) { watchers = _.union(watchers, oldList.watchers || []); params.oldList = oldList.title; @@ -176,7 +176,7 @@ if (Meteor.isServer) { } } if (activity.oldSwimlaneId) { - const oldSwimlane = await activity.oldSwimlane(); + const oldSwimlane = activity.oldSwimlane(); if (oldSwimlane) { watchers = _.union(watchers, oldSwimlane.watchers || []); params.oldSwimlane = oldSwimlane.title; @@ -184,7 +184,7 @@ if (Meteor.isServer) { } } if (activity.cardId) { - const card = await activity.card(); + const card = activity.card(); participants = _.union(participants, [card.userId], card.members || []); watchers = _.union(watchers, card.watchers || []); params.card = card.title; @@ -193,100 +193,44 @@ if (Meteor.isServer) { params.cardId = activity.cardId; } if (activity.swimlaneId) { - const swimlane = await activity.swimlane(); + const swimlane = activity.swimlane(); params.swimlane = swimlane.title; params.swimlaneId = activity.swimlaneId; } if (activity.commentId) { - const comment = await activity.comment(); + const comment = activity.comment(); params.comment = comment.text; - let hasMentions = false; // Track if comment has @mentions if (board) { const comment = params.comment; - // Build knownUsers with async user lookups - const knownUsers = []; - for (const member of board.members) { - const u = await ReactiveCache.getUser(member.userId); + const knownUsers = board.members.map(member => { + const u = ReactiveCache.getUser(member.userId); if (u) { member.username = u.username; member.emails = u.emails; } - knownUsers.push(member); - } - // Match @mentions including usernames with @ symbols (like email addresses) - // Pattern matches: @username, @user@example.com, @"quoted username" - const mentionRegex = /\B@(?:(?:"([\w.\s-]*)")|([\w.@-]+))/gi; + return member; + }); + const mentionRegex = /\B@(?:(?:"([\w.\s-]*)")|([\w.-]+))/gi; // including space in username let currentMention; - while ((currentMention = mentionRegex.exec(comment)) !== null) { /*eslint no-unused-vars: ["error", { "varsIgnorePattern": "[iI]gnored" }]*/ const [ignored, quoteduser, simple] = currentMention; const username = quoteduser || simple; - // Removed the check that prevented self-mentions from creating notifications - // Users can now mention themselves in comments to create notifications + if (username === params.user) { + // ignore commenter mention himself? + continue; + } if (activity.boardId && username === 'board_members') { // mentions all board members - const validUserIds = []; - for (const u of knownUsers) { - const user = await ReactiveCache.getUser(u.userId); - if (user && user._id) { - validUserIds.push(u.userId); - } - } - watchers = _.union(watchers, validUserIds); + const knownUids = knownUsers.map(u => u.userId); + watchers = _.union(watchers, [...knownUids]); title = 'act-atUserComment'; - hasMentions = true; - } else if (activity.boardId && username === 'board_assignees') { - // mentions all assignees of all cards on the board - const allCards = await ReactiveCache.getCards({ boardId: activity.boardId }); - const assigneeIds = []; - for (const card of allCards) { - if (card.assignees && card.assignees.length > 0) { - for (const assigneeId of card.assignees) { - // Only add if the user exists and is a board member - const user = await ReactiveCache.getUser(assigneeId); - if (user && _.findWhere(knownUsers, { userId: assigneeId })) { - assigneeIds.push(assigneeId); - } - } - } - } - watchers = _.union(watchers, assigneeIds); - title = 'act-atUserComment'; - hasMentions = true; } else if (activity.cardId && username === 'card_members') { // mentions all card members if assigned - const card = await activity.card(); - if (card && card.members && card.members.length > 0) { - // Filter to only valid users who are board members - const validMembers = []; - for (const memberId of card.members) { - const user = await ReactiveCache.getUser(memberId); - if (user && user._id && _.findWhere(knownUsers, { userId: memberId })) { - validMembers.push(memberId); - } - } - watchers = _.union(watchers, validMembers); - } + const card = activity.card(); + watchers = _.union(watchers, [...card.members]); title = 'act-atUserComment'; - hasMentions = true; - } else if (activity.cardId && username === 'card_assignees') { - // mentions all assignees of the current card - const card = await activity.card(); - if (card && card.assignees && card.assignees.length > 0) { - // Filter to only valid users who are board members - const validAssignees = []; - for (const assigneeId of card.assignees) { - const user = await ReactiveCache.getUser(assigneeId); - if (user && user._id && _.findWhere(knownUsers, { userId: assigneeId })) { - validAssignees.push(assigneeId); - } - } - watchers = _.union(watchers, validAssignees); - } - title = 'act-atUserComment'; - hasMentions = true; } else { const atUser = _.findWhere(knownUsers, { username }); if (!atUser) { @@ -298,19 +242,18 @@ if (Meteor.isServer) { params.atEmails = atUser.emails; title = 'act-atUserComment'; watchers = _.union(watchers, [uid]); - hasMentions = true; } + } } params.commentId = comment._id; - params.hasMentions = hasMentions; // Store for later use } if (activity.attachmentId) { params.attachment = activity.attachmentName; params.attachmentId = activity.attachmentId; } if (activity.checklistId) { - const checklist = await activity.checklist(); + const checklist = activity.checklist(); if (checklist) { if (checklist.title) { params.checklist = checklist.title; @@ -318,7 +261,7 @@ if (Meteor.isServer) { } } if (activity.checklistItemId) { - const checklistItem = await activity.checklistItem(); + const checklistItem = activity.checklistItem(); if (checklistItem) { if (checklistItem.title) { params.checklistItem = checklistItem.title; @@ -326,7 +269,7 @@ if (Meteor.isServer) { } } if (activity.customFieldId) { - const customField = await activity.customField(); + const customField = activity.customField(); if (customField) { if (customField.name) { params.customField = customField.name; @@ -338,7 +281,7 @@ if (Meteor.isServer) { } // Label activity did not work yet, unable to edit labels when tried this. if (activity.labelId) { - const label = await activity.label(); + const label = activity.label(); if (label) { if (label.name) { params.label = label.name; @@ -357,19 +300,21 @@ if (Meteor.isServer) { // due time reminder, if it doesn't have old value, it's a brand new set, need some differentiation title = activity.timeOldValue ? 'act-withDue' : 'act-newDue'; } - ['timeValue', 'timeOldValue'].forEach((key) => { + ['timeValue', 'timeOldValue'].forEach(key => { // copy time related keys & values to params const value = activity[key]; if (value) params[key] = value; }); if (board) { - const activeMemberIds = _.filter(board.members || [], m => m.isActive === true).map(m => m.userId); const BIGEVENTS = process.env.BIGEVENTS_PATTERN; // if environment BIGEVENTS_PATTERN is set, any activityType matching it is important if (BIGEVENTS) { try { const atype = activity.activityType; if (new RegExp(BIGEVENTS).exec(atype)) { - watchers = _.union(watchers, activeMemberIds); // notify all active members for important events + watchers = _.union( + watchers, + board.activeMembers().map(member => member.userId), + ); // notify all active members for important events } } catch (e) { // passed env var BIGEVENTS_PATTERN is not a valid regex @@ -384,31 +329,20 @@ if (Meteor.isServer) { _.where(board.watchers, { level: 'tracking' }), 'userId', ); - // Only add board watchers if there were no @mentions in the comment - // When users are explicitly @mentioned, only notify those users - if (!params.hasMentions) { - watchers = _.union( - watchers, - watchingUsers, - _.intersection(participants, trackingUsers), - ); - } - - // Ensure notifications only go to active members of the current board. - watchers = _.intersection(watchers, activeMemberIds); + watchers = _.union( + watchers, + watchingUsers, + _.intersection(participants, trackingUsers), + ); } - (await Notifications.getUsers(watchers)).forEach((user) => { - // Skip if user is undefined or doesn't have an _id (e.g., deleted user or invalid ID) - if (!user || !user._id) return; - - // Don't notify a user of their own behavior, EXCEPT for self-mentions - const isSelfMention = (user._id === userId && title === 'act-atUserComment'); - if (user._id !== userId || isSelfMention) { + Notifications.getUsers(watchers).forEach(user => { + // don't notify a user of their own behavior + if (user._id !== userId) { Notifications.notify(user, title, description, params); } }); - const integrations = await ReactiveCache.getIntegrations({ + const integrations = ReactiveCache.getIntegrations({ boardId: { $in: [board._id, Integrations.Const.GLOBAL_WEBHOOK_ID] }, // type: 'outgoing-webhooks', // all types enabled: true, @@ -416,7 +350,7 @@ if (Meteor.isServer) { }); if (integrations.length > 0) { params.watchers = watchers; - integrations.forEach((integration) => { + integrations.forEach(integration => { Meteor.call( 'outgoingWebhooks', integration, diff --git a/models/announcements.js b/models/announcements.js index a4f3455b4..5458cd70b 100644 --- a/models/announcements.js +++ b/models/announcements.js @@ -51,14 +51,14 @@ Announcements.attachSchema( Announcements.allow({ update(userId) { - const user = Meteor.users.findOne(userId); + const user = ReactiveCache.getUser(userId); return user && user.isAdmin; }, }); if (Meteor.isServer) { - Meteor.startup(async () => { - await Announcements._collection.createIndexAsync({ modifiedAt: -1 }); + Meteor.startup(() => { + Announcements._collection.createIndex({ modifiedAt: -1 }); const announcements = Announcements.findOne({}); if (!announcements) { Announcements.insert({ enabled: false, sort: 0 }); diff --git a/models/attachmentStorageSettings.js b/models/attachmentStorageSettings.js index 550bafcd3..f0a67271c 100644 --- a/models/attachmentStorageSettings.js +++ b/models/attachmentStorageSettings.js @@ -18,44 +18,44 @@ AttachmentStorageSettings.attachSchema( defaultValue: STORAGE_NAME_FILESYSTEM, label: 'Default Storage Backend' }, - + // Storage backend configuration storageConfig: { type: Object, optional: true, label: 'Storage Configuration' }, - + 'storageConfig.filesystem': { type: Object, optional: true, label: 'Filesystem Configuration' }, - + 'storageConfig.filesystem.enabled': { type: Boolean, defaultValue: true, label: 'Filesystem Storage Enabled' }, - + 'storageConfig.filesystem.path': { type: String, optional: true, label: 'Filesystem Storage Path' }, - + 'storageConfig.gridfs': { type: Object, optional: true, label: 'GridFS Configuration' }, - + 'storageConfig.gridfs.enabled': { type: Boolean, defaultValue: true, label: 'GridFS Storage Enabled' }, - + // DISABLED: S3 storage configuration removed due to Node.js compatibility /* 'storageConfig.s3': { @@ -63,81 +63,81 @@ AttachmentStorageSettings.attachSchema( optional: true, label: 'S3 Configuration' }, - + 'storageConfig.s3.enabled': { type: Boolean, defaultValue: false, label: 'S3 Storage Enabled' }, - + 'storageConfig.s3.endpoint': { type: String, optional: true, label: 'S3 Endpoint' }, - + 'storageConfig.s3.bucket': { type: String, optional: true, label: 'S3 Bucket' }, - + 'storageConfig.s3.region': { type: String, optional: true, label: 'S3 Region' }, - + 'storageConfig.s3.sslEnabled': { type: Boolean, defaultValue: true, label: 'S3 SSL Enabled' }, - + 'storageConfig.s3.port': { type: Number, defaultValue: 443, label: 'S3 Port' }, */ - + // Upload settings uploadSettings: { type: Object, optional: true, label: 'Upload Settings' }, - + 'uploadSettings.maxFileSize': { type: Number, optional: true, label: 'Maximum File Size (bytes)' }, - + 'uploadSettings.allowedMimeTypes': { type: Array, optional: true, label: 'Allowed MIME Types' }, - + 'uploadSettings.allowedMimeTypes.$': { type: String, label: 'MIME Type' }, - + // Migration settings migrationSettings: { type: Object, optional: true, label: 'Migration Settings' }, - + 'migrationSettings.autoMigrate': { type: Boolean, defaultValue: false, label: 'Auto Migrate to Default Storage' }, - + 'migrationSettings.batchSize': { type: Number, defaultValue: 10, @@ -145,7 +145,7 @@ AttachmentStorageSettings.attachSchema( max: 100, label: 'Migration Batch Size' }, - + 'migrationSettings.delayMs': { type: Number, defaultValue: 1000, @@ -153,7 +153,7 @@ AttachmentStorageSettings.attachSchema( max: 10000, label: 'Migration Delay (ms)' }, - + 'migrationSettings.cpuThreshold': { type: Number, defaultValue: 70, @@ -161,7 +161,7 @@ AttachmentStorageSettings.attachSchema( max: 90, label: 'CPU Threshold (%)' }, - + // Metadata createdAt: { type: Date, @@ -176,7 +176,7 @@ AttachmentStorageSettings.attachSchema( }, label: 'Created At' }, - + updatedAt: { type: Date, autoValue() { @@ -186,13 +186,13 @@ AttachmentStorageSettings.attachSchema( }, label: 'Updated At' }, - + createdBy: { type: String, optional: true, label: 'Created By' }, - + updatedBy: { type: String, optional: true, @@ -207,11 +207,11 @@ AttachmentStorageSettings.helpers({ getDefaultStorage() { return this.defaultStorage || STORAGE_NAME_FILESYSTEM; }, - + // Check if storage backend is enabled isStorageEnabled(storageName) { if (!this.storageConfig) return false; - + switch (storageName) { case STORAGE_NAME_FILESYSTEM: return this.storageConfig.filesystem?.enabled !== false; @@ -224,11 +224,11 @@ AttachmentStorageSettings.helpers({ return false; } }, - + // Get storage configuration getStorageConfig(storageName) { if (!this.storageConfig) return null; - + switch (storageName) { case STORAGE_NAME_FILESYSTEM: return this.storageConfig.filesystem; @@ -241,12 +241,12 @@ AttachmentStorageSettings.helpers({ return null; } }, - + // Get upload settings getUploadSettings() { return this.uploadSettings || {}; }, - + // Get migration settings getMigrationSettings() { return this.migrationSettings || {}; @@ -257,18 +257,18 @@ AttachmentStorageSettings.helpers({ if (Meteor.isServer) { // Get or create default settings Meteor.methods({ - async 'getAttachmentStorageSettings'() { + 'getAttachmentStorageSettings'() { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } - const user = await ReactiveCache.getUser(this.userId); + const user = ReactiveCache.getUser(this.userId); if (!user || !user.isAdmin) { throw new Meteor.Error('not-authorized', 'Admin access required'); } let settings = AttachmentStorageSettings.findOne({}); - + if (!settings) { // Create default settings settings = { @@ -299,20 +299,20 @@ if (Meteor.isServer) { createdBy: this.userId, updatedBy: this.userId }; - + AttachmentStorageSettings.insert(settings); settings = AttachmentStorageSettings.findOne({}); } - + return settings; }, - async 'updateAttachmentStorageSettings'(settings) { + 'updateAttachmentStorageSettings'(settings) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } - const user = await ReactiveCache.getUser(this.userId); + const user = ReactiveCache.getUser(this.userId); if (!user || !user.isAdmin) { throw new Meteor.Error('not-authorized', 'Admin access required'); } @@ -320,7 +320,7 @@ if (Meteor.isServer) { // Validate settings const schema = AttachmentStorageSettings.simpleSchema(); schema.validate(settings); - + // Update settings const result = AttachmentStorageSettings.upsert( {}, @@ -332,10 +332,10 @@ if (Meteor.isServer) { } } ); - + return result; }, - + 'getDefaultAttachmentStorage'() { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); @@ -345,12 +345,12 @@ if (Meteor.isServer) { return settings ? settings.getDefaultStorage() : STORAGE_NAME_FILESYSTEM; }, - async 'setDefaultAttachmentStorage'(storageName) { + 'setDefaultAttachmentStorage'(storageName) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } - const user = await ReactiveCache.getUser(this.userId); + const user = ReactiveCache.getUser(this.userId); if (!user || !user.isAdmin) { throw new Meteor.Error('not-authorized', 'Admin access required'); } @@ -369,18 +369,18 @@ if (Meteor.isServer) { } } ); - + return result; } }); // Publication for settings - Meteor.publish('attachmentStorageSettings', async function() { + Meteor.publish('attachmentStorageSettings', function() { if (!this.userId) { return this.ready(); } - const user = await ReactiveCache.getUser(this.userId); + const user = ReactiveCache.getUser(this.userId); if (!user || !user.isAdmin) { return this.ready(); } diff --git a/models/attachments.js b/models/attachments.js index 308780462..2c5af186e 100644 --- a/models/attachments.js +++ b/models/attachments.js @@ -98,27 +98,6 @@ Attachments = new FilesCollection({ return ret; }, onBeforeUpload(file) { - // SECURITY: Sanitize filename to prevent path traversal attacks - // Import sanitizeFilename from fileStoreStrategy - but since we can't import here, - // we'll implement a minimal inline version to be safe - if (file.name && typeof file.name === 'string') { - // Use path.basename to strip directory components and prevent path traversal - let safeName = path.basename(file.name); - // Remove null bytes - safeName = safeName.replace(/\0/g, ''); - // Remove path traversal sequences - safeName = safeName.replace(/\.\.[\\/\\]/g, ''); - safeName = safeName.replace(/^\.\.$/g, ''); - safeName = safeName.trim(); - - // If sanitization changed the name, update it - if (safeName && safeName !== '.' && safeName !== '..' && safeName !== file.name) { - file.name = safeName; - } else if (!safeName || safeName === '.' || safeName === '..') { - file.name = 'unnamed'; - } - } - // Block SVG files for attachments to prevent XSS attacks if (file.name && file.name.toLowerCase().endsWith('.svg')) { if (process.env.DEBUG === 'true') { @@ -159,7 +138,7 @@ Attachments = new FilesCollection({ // Use selected storage backend or copy storage if specified let storageDestination = fileObj.meta.copyStorage || defaultStorage; - + // Only migrate if the destination is different from filesystem if (storageDestination !== STORAGE_NAME_FILESYSTEM) { Meteor.defer(() => Meteor.call('validateAttachmentAndMoveToStorage', fileObj._id, storageDestination)); @@ -169,14 +148,8 @@ Attachments = new FilesCollection({ const ret = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName).interceptDownload(http, this.cacheControl); return ret; }, - onAfterRemove(filesInput) { - const files = normalizeRemovedFiles(filesInput); - + onAfterRemove(files) { files.forEach(fileObj => { - if (!fileObj || !fileObj.versions) { - return; - } - Object.keys(fileObj.versions).forEach(versionName => { fileStoreStrategyFactory.getFileStrategy(fileObj, versionName).onAfterRemove(); }); @@ -185,13 +158,13 @@ Attachments = new FilesCollection({ // We authorize the attachment download either: // - if the board is public, everyone (even unconnected) can download it // - if the board is private, only board members can download it - async protected(fileObj) { + protected(fileObj) { // file may have been deleted already again after upload validation failed if (!fileObj) { return false; } - const board = await ReactiveCache.getBoard(fileObj.meta.boardId); + const board = ReactiveCache.getBoard(fileObj.meta.boardId); if (board.isPublic()) { return true; } @@ -200,65 +173,15 @@ Attachments = new FilesCollection({ }, }); -function normalizeRemovedFiles(filesInput) { - if (!filesInput) { - return []; - } - - if (Array.isArray(filesInput)) { - return filesInput; - } - - if (typeof filesInput.fetch === 'function') { - return filesInput.fetch(); - } - - if (Array.isArray(filesInput.files)) { - return filesInput.files; - } - - if (typeof filesInput === 'string') { - return Attachments.find({ _id: filesInput }).fetch(); - } - - if (filesInput && typeof filesInput === 'object') { - if (filesInput._id && (filesInput.versions || filesInput.meta)) { - return [filesInput]; - } - - return Attachments.find(filesInput).fetch(); - } - - return []; -} - if (Meteor.isServer) { Attachments.allow({ insert(userId, fileObj) { - // ReadOnly users cannot upload attachments - return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(fileObj.boardId)); + return allowIsBoardMember(userId, ReactiveCache.getBoard(fileObj.boardId)); }, update(userId, fileObj, fields) { - // SECURITY: The 'name' field is sanitized in onBeforeUpload and server-side methods, - // but we block direct client-side $set operations on 'versions.*.path' to prevent - // path traversal attacks via storage migration exploits. - - // Block direct updates to version paths (the attack vector) - const hasPathUpdate = fields.some(field => field.includes('versions') && field.includes('path')); - if (hasPathUpdate) { - if (process.env.DEBUG === 'true') { - console.warn('Blocked attempt to update attachment version paths:', fields); - } - return false; - } - - // Allow normal updates for file upload/management + // Only allow updates to specific fields that don't affect security const allowedFields = ['name', 'size', 'type', 'extension', 'extensionWithDot', 'meta', 'versions']; - const isAllowedField = fields.every(field => { - // Allow field itself or nested properties like 'versions.original' - const baseField = field.split('.')[0]; - return allowedFields.includes(baseField); - }); + const isAllowedField = fields.every(field => allowedFields.includes(field)); if (!isAllowedField) { if (process.env.DEBUG === 'true') { @@ -267,8 +190,7 @@ if (Meteor.isServer) { return false; } - // ReadOnly users cannot update attachments - return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(fileObj.boardId)); + return allowIsBoardMember(userId, ReactiveCache.getBoard(fileObj.boardId)); }, remove(userId, fileObj) { // Additional security check: ensure the file belongs to the board the user has access to @@ -279,7 +201,7 @@ if (Meteor.isServer) { return false; } - const board = Boards.findOne(fileObj.boardId); + const board = ReactiveCache.getBoard(fileObj.boardId); if (!board) { if (process.env.DEBUG === 'true') { console.warn('Blocked attachment removal: board not found'); @@ -287,8 +209,7 @@ if (Meteor.isServer) { return false; } - // ReadOnly users cannot delete attachments - return allowIsBoardMemberWithWriteAccess(userId, board); + return allowIsBoardMember(userId, board); }, fetch: ['meta', 'boardId'], }); @@ -331,33 +252,14 @@ if (Meteor.isServer) { return { valid: true }; }, - async moveAttachmentToStorage(fileObjId, storageDestination) { + moveAttachmentToStorage(fileObjId, storageDestination) { check(fileObjId, String); check(storageDestination, String); - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const fileObj = await ReactiveCache.getAttachment(fileObjId); - if (!fileObj) { - throw new Meteor.Error('attachment-not-found', 'Attachment not found'); - } - - const board = await ReactiveCache.getBoard(fileObj.boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - - // Allowlist storage destinations - const allowedDestinations = ['fs', 'gridfs', 's3']; - if (!allowedDestinations.includes(storageDestination)) { - throw new Meteor.Error('invalid-storage-destination', 'Invalid storage destination'); - } - + const fileObj = ReactiveCache.getAttachment(fileObjId); moveToStorage(fileObj, storageDestination, fileStoreStrategyFactory); }, - async renameAttachment(fileObjId, newName) { + renameAttachment(fileObjId, newName) { check(fileObjId, String); check(newName, String); @@ -366,13 +268,13 @@ if (Meteor.isServer) { throw new Meteor.Error('not-authorized', 'User must be logged in'); } - const fileObj = await ReactiveCache.getAttachment(fileObjId); + const fileObj = ReactiveCache.getAttachment(fileObjId); if (!fileObj) { throw new Meteor.Error('file-not-found', 'Attachment not found'); } // Verify the user has permission to modify this attachment - const board = await ReactiveCache.getBoard(fileObj.boardId); + const board = ReactiveCache.getBoard(fileObj.boardId); if (!board) { throw new Meteor.Error('board-not-found', 'Board not found'); } @@ -386,65 +288,32 @@ if (Meteor.isServer) { rename(fileObj, newName, fileStoreStrategyFactory); }, - async validateAttachment(fileObjId) { + validateAttachment(fileObjId) { check(fileObjId, String); - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const fileObj = await ReactiveCache.getAttachment(fileObjId); - if (!fileObj) { - throw new Meteor.Error('attachment-not-found', 'Attachment not found'); - } - - const board = await ReactiveCache.getBoard(fileObj.boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - - const isValid = await isFileValid(fileObj, attachmentUploadMimeTypes, attachmentUploadSize, attachmentUploadExternalProgram); + const fileObj = ReactiveCache.getAttachment(fileObjId); + const isValid = Promise.await(isFileValid(fileObj, attachmentUploadMimeTypes, attachmentUploadSize, attachmentUploadExternalProgram)); if (!isValid) { Attachments.remove(fileObjId); } }, - async validateAttachmentAndMoveToStorage(fileObjId, storageDestination) { + validateAttachmentAndMoveToStorage(fileObjId, storageDestination) { check(fileObjId, String); check(storageDestination, String); - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } + Meteor.call('validateAttachment', fileObjId); - const fileObj = await ReactiveCache.getAttachment(fileObjId); - if (!fileObj) { - throw new Meteor.Error('attachment-not-found', 'Attachment not found'); - } + const fileObj = ReactiveCache.getAttachment(fileObjId); - const board = await ReactiveCache.getBoard(fileObj.boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - - // Allowlist storage destinations - const allowedDestinations = ['fs', 'gridfs', 's3']; - if (!allowedDestinations.includes(storageDestination)) { - throw new Meteor.Error('invalid-storage-destination', 'Invalid storage destination'); - } - - await Meteor.callAsync('validateAttachment', fileObjId); - - const fileObjAfter = await ReactiveCache.getAttachment(fileObjId); - - if (fileObjAfter) { + if (fileObj) { Meteor.defer(() => Meteor.call('moveAttachmentToStorage', fileObjId, storageDestination)); } }, }); - Meteor.startup(async () => { - await Attachments.collection.createIndexAsync({ 'meta.cardId': 1 }); + Meteor.startup(() => { + Attachments.collection.createIndex({ 'meta.cardId': 1 }); const storagePath = fileStoreStrategyFactory.storagePath; if (!fs.existsSync(storagePath)) { console.log("create storagePath because it doesn't exist: " + storagePath); @@ -466,7 +335,7 @@ if (Meteor.isClient) { // Accept both direct calls and collection.helpers style calls const fileRef = this._id ? this : (versionName && versionName._id ? versionName : this); const version = (typeof versionName === 'string') ? versionName : 'original'; - + if (fileRef && fileRef._id) { const url = generateUniversalAttachmentUrl(fileRef._id, version); if (process.env.DEBUG === 'true') { @@ -477,7 +346,7 @@ if (Meteor.isClient) { // Fallback to original if somehow we don't have an ID return originalLink ? originalLink.call(this, versionName) : ''; }; - + // Also add as collection helper for document instances Attachments.collection.helpers({ link(version) { diff --git a/models/avatars.js b/models/avatars.js index c806d6804..da3033bc8 100644 --- a/models/avatars.js +++ b/models/avatars.js @@ -106,7 +106,7 @@ Avatars = new FilesCollection({ } return TAPi18n.__('avatar-too-big', {size: filesize(avatarsUploadSize)}); }, - async onAfterUpload(fileObj) { + onAfterUpload(fileObj) { // current storage is the filesystem, update object and database Object.keys(fileObj.versions).forEach(versionName => { fileObj.versions[versionName].storage = STORAGE_NAME_FILESYSTEM; @@ -114,13 +114,12 @@ Avatars = new FilesCollection({ Avatars.update({ _id: fileObj._id }, { $set: { "versions": fileObj.versions } }); - const isValid = await isFileValid(fileObj, avatarsUploadMimeTypes, avatarsUploadSize, avatarsUploadExternalProgram); + const isValid = Promise.await(isFileValid(fileObj, avatarsUploadMimeTypes, avatarsUploadSize, avatarsUploadExternalProgram)); if (isValid) { // Set avatar URL using universal URL generator (URL-agnostic) const universalUrl = generateUniversalAvatarUrl(fileObj._id); - const user = await ReactiveCache.getUser(fileObj.userId); - user.setAvatarUrl(universalUrl); + ReactiveCache.getUser(fileObj.userId).setAvatarUrl(universalUrl); } else { Avatars.remove(fileObj._id); } @@ -129,26 +128,17 @@ Avatars = new FilesCollection({ const ret = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName).interceptDownload(http, this.cacheControl); return ret; }, - async onBeforeRemove(filesInput) { - const files = normalizeRemovedFiles(filesInput); - - for (const fileObj of files) { - if (fileObj && fileObj.userId) { - const user = await ReactiveCache.getUser(fileObj.userId); - user.setAvatarUrl(''); + onBeforeRemove(files) { + files.forEach(fileObj => { + if (fileObj.userId) { + ReactiveCache.getUser(fileObj.userId).setAvatarUrl(''); } - } + }); return true; }, - onAfterRemove(filesInput) { - const files = normalizeRemovedFiles(filesInput); - + onAfterRemove(files) { files.forEach(fileObj => { - if (!fileObj || !fileObj.versions) { - return; - } - Object.keys(fileObj.versions).forEach(versionName => { fileStoreStrategyFactory.getFileStrategy(fileObj, versionName).onAfterRemove(); }); @@ -156,38 +146,6 @@ Avatars = new FilesCollection({ }, }); -function normalizeRemovedFiles(filesInput) { - if (!filesInput) { - return []; - } - - if (Array.isArray(filesInput)) { - return filesInput; - } - - if (typeof filesInput.fetch === 'function') { - return filesInput.fetch(); - } - - if (Array.isArray(filesInput.files)) { - return filesInput.files; - } - - if (typeof filesInput === 'string') { - return Avatars.find({ _id: filesInput }).fetch(); - } - - if (filesInput && typeof filesInput === 'object') { - if (filesInput._id && (filesInput.versions || filesInput.userId)) { - return [filesInput]; - } - - return Avatars.find(filesInput).fetch(); - } - - return []; -} - function isOwner(userId, doc) { return userId && userId === doc.userId; } diff --git a/models/boards.js b/models/boards.js index 223699e84..569bb5e78 100644 --- a/models/boards.js +++ b/models/boards.js @@ -1,7 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; import escapeForRegex from 'escape-string-regexp'; import { TAPi18n } from '/imports/i18n'; -import { CustomFields } from './customFields'; import { ALLOWED_BOARD_COLORS, ALLOWED_COLORS, @@ -10,9 +9,6 @@ import { TYPE_TEMPLATE_CONTAINER, } from '/config/const'; import Users from "./users"; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; -import TableVisibilityModeSettings from "./tableVisibilityModeSettings"; -import getSlug from 'limax'; // const escapeForRegex = require('escape-string-regexp'); @@ -229,34 +225,6 @@ Boards.attachSchema( type: Boolean, optional: true, }, - 'members.$.isNormalAssignedOnly': { - /** - * Is the member only allowed to see assigned cards (Normal permission) - */ - type: Boolean, - optional: true, - }, - 'members.$.isCommentAssignedOnly': { - /** - * Is the member only allowed to comment on assigned cards - */ - type: Boolean, - optional: true, - }, - 'members.$.isReadOnly': { - /** - * Is the member only allowed to read the board (no comments, no editing) - */ - type: Boolean, - optional: true, - }, - 'members.$.isReadAssignedOnly': { - /** - * Is the member only allowed to read assigned cards (no comments, no editing) - */ - type: Boolean, - optional: true, - }, permission: { /** * visibility of the board @@ -573,14 +541,6 @@ Boards.attachSchema( defaultValue: false, }, - allowsChecklistAtMinicard: { - /** - * Does the board allow showing checklists on all minicards? - */ - type: Boolean, - defaultValue: false, - }, - allowsReceivedDate: { /** * Does the board allows received date? @@ -703,12 +663,12 @@ Boards.attachSchema( ); Boards.helpers({ - async copy() { + copy() { const oldId = this._id; const oldWatchers = this.watchers ? this.watchers.slice() : []; delete this._id; delete this.slug; - this.title = await this.copyTitle(); + this.title = this.copyTitle(); const _id = Boards.insert(this); // Temporary remove watchers to disable notifications @@ -719,26 +679,23 @@ Boards.helpers({ }); // Copy all swimlanes in board - const swimlanes = await ReactiveCache.getSwimlanes({ + ReactiveCache.getSwimlanes({ boardId: oldId, archived: false, - }); - for (const swimlane of swimlanes) { + }).forEach(swimlane => { swimlane.type = 'swimlane'; swimlane.copy(_id); - } + }); // copy custom field definitions const cfMap = {}; - const customFields = await ReactiveCache.getCustomFields({ boardIds: oldId }); - for (const cf of customFields) { + ReactiveCache.getCustomFields({ boardIds: oldId }).forEach(cf => { const id = cf._id; delete cf._id; cf.boardIds = [_id]; cfMap[id] = CustomFields.insert(cf); - } - const cards = await ReactiveCache.getCards({ boardId: _id }); - for (const card of cards) { + }); + ReactiveCache.getCards({ boardId: _id }).forEach(card => { Cards.update(card._id, { $set: { customFields: card.customFields.map(cf => { @@ -747,33 +704,30 @@ Boards.helpers({ }), }, }); - } + }); // copy rules, actions, and triggers const actionsMap = {}; - const actions = await ReactiveCache.getActions({ boardId: oldId }); - for (const action of actions) { + ReactiveCache.getActions({ boardId: oldId }).forEach(action => { const id = action._id; delete action._id; action.boardId = _id; actionsMap[id] = Actions.insert(action); - } + }); const triggersMap = {}; - const triggers = await ReactiveCache.getTriggers({ boardId: oldId }); - for (const trigger of triggers) { + ReactiveCache.getTriggers({ boardId: oldId }).forEach(trigger => { const id = trigger._id; delete trigger._id; trigger.boardId = _id; triggersMap[id] = Triggers.insert(trigger); - } - const rules = await ReactiveCache.getRules({ boardId: oldId }); - for (const rule of rules) { + }); + ReactiveCache.getRules({ boardId: oldId }).forEach(rule => { delete rule._id; rule.boardId = _id; rule.actionId = actionsMap[rule.actionId]; rule.triggerId = triggersMap[rule.triggerId]; Rules.insert(rule); - } + }); // Re-set Watchers to reenable notification Boards.update(_id, { @@ -787,8 +741,8 @@ Boards.helpers({ * * @returns {string|null} */ - async copyTitle() { - return await Boards.uniqueTitle(this.title); + copyTitle() { + return Boards.uniqueTitle(this.title); }, /** @@ -843,8 +797,7 @@ Boards.helpers({ newestLists() { // sorted lists from newest to the oldest, by its creation date or its cards' last modification date - const user = ReactiveCache.getCurrentUser(); - const value = user._getListSortBy(); + const value = ReactiveCache.getCurrentUser()._getListSortBy(); const sortKey = { starred: -1, [value[0]]: value[1] }; // [["starred",-1],value]; return ReactiveCache.getLists( { @@ -931,51 +884,17 @@ Boards.helpers({ let linkedBoardId = [this._id]; ReactiveCache.getCards({ "type": "cardType-linkedBoard", - "boardId": this._id - }).forEach(card => { - linkedBoardId.push(card.linkedId); + "boardId": this._id} + ).forEach(card => { + linkedBoardId.push(card.linkedId); }); const ret = ReactiveCache.getActivities({ boardId: { $in: linkedBoardId } }, { sort: { createdAt: -1 } }); return ret; }, activeMembers(){ - // Depend on the users collection for reactivity when users are loaded - const memberUserIds = _.pluck(this.members, 'userId'); - // Use findOne with limit for reactivity trigger instead of count() which loads all users - const dummy = Meteor.users.findOne({ _id: { $in: memberUserIds } }, { fields: { _id: 1 }, limit: 1 }); - const members = _.filter(this.members, m => m.isActive === true); - // Group by userId to handle duplicates - const grouped = _.groupBy(members, 'userId'); - const uniqueMembers = _.values(grouped).map(group => { - // Prefer admin member if exists, otherwise take the first - const selected = _.find(group, m => m.isAdmin) || group[0]; - return selected; - }); - // Filter out members where user is not loaded - const filteredMembers = uniqueMembers.filter(member => { - const user = ReactiveCache.getUser(member.userId); - return user !== undefined; - }); - - // Sort by role priority first (admin, normal, normal-assigned, no-comments, comment-only, comment-assigned, worker, read-only, read-assigned), then by fullname - return _.sortBy(filteredMembers, member => { - const user = ReactiveCache.getUser(member.userId); - let rolePriority = 8; // Default for normal - - if (member.isAdmin) rolePriority = 0; - else if (member.isReadAssignedOnly) rolePriority = 8; - else if (member.isReadOnly) rolePriority = 7; - else if (member.isWorker) rolePriority = 6; - else if (member.isCommentAssignedOnly) rolePriority = 5; - else if (member.isCommentOnly) rolePriority = 4; - else if (member.isNoComments) rolePriority = 3; - else if (member.isNormalAssignedOnly) rolePriority = 2; - else rolePriority = 1; // Normal - - const fullname = user ? user.profile.fullname : ''; - return rolePriority + '-' + fullname; - }); + const members = _.where(this.members, { isActive: true }); + return _.sortBy(members, 'username'); }, activeOrgs() { @@ -1060,44 +979,6 @@ Boards.helpers({ }); }, - hasNormalAssignedOnly(memberId) { - return !!_.findWhere(this.members, { - userId: memberId, - isActive: true, - isAdmin: false, - isNormalAssignedOnly: true, - isCommentAssignedOnly: false, - }); - }, - - hasCommentAssignedOnly(memberId) { - return !!_.findWhere(this.members, { - userId: memberId, - isActive: true, - isAdmin: false, - isNormalAssignedOnly: false, - isCommentAssignedOnly: true, - }); - }, - - hasReadOnly(memberId) { - return !!_.findWhere(this.members, { - userId: memberId, - isActive: true, - isAdmin: false, - isReadOnly: true, - }); - }, - - hasReadAssignedOnly(memberId) { - return !!_.findWhere(this.members, { - userId: memberId, - isActive: true, - isAdmin: false, - isReadAssignedOnly: true, - }); - }, - hasAnyAllowsDate() { const ret = this.allowsReceivedDate || this.allowsStartDate || this.allowsDueDate || this.allowsEndDate; return ret; @@ -1109,10 +990,10 @@ Boards.helpers({ }, absoluteUrl() { - return FlowRouter.url('board', { id: this._id, slug: this.slug || 'board' }); + return FlowRouter.url('board', { id: this._id, slug: this.slug }); }, originRelativeUrl() { - return FlowRouter.path('board', { id: this._id, slug: this.slug || 'board' }); + return FlowRouter.path('board', { id: this._id, slug: this.slug }); }, colorClass() { @@ -1440,80 +1321,93 @@ Boards.helpers({ isTemplatesBoard() { return this.type === 'template-container'; }, +}); - async archive() { - return await Boards.updateAsync(this._id, { $set: { archived: true, archivedAt: new Date() } }); +Boards.mutations({ + archive() { + return { $set: { archived: true, archivedAt: new Date() } }; }, - async restore() { - return await Boards.updateAsync(this._id, { $set: { archived: false } }); + restore() { + return { $set: { archived: false } }; }, - async rename(title) { - return await Boards.updateAsync(this._id, { $set: { title } }); + rename(title) { + return { $set: { title } }; }, - async setDescription(description) { - return await Boards.updateAsync(this._id, { $set: { description } }); + setDescription(description) { + return { $set: { description } }; }, - async setColor(color) { - return await Boards.updateAsync(this._id, { $set: { color } }); + setColor(color) { + return { $set: { color } }; }, - async setBackgroundImageURL(backgroundImageURL) { - const currentUser = await ReactiveCache.getCurrentUser(); - if (currentUser.isBoardAdmin() || currentUser.isAdmin()) { - return await Boards.updateAsync(this._id, { $set: { backgroundImageURL } }); + setBackgroundImageURL(backgroundImageURL) { + const currentUser = ReactiveCache.getCurrentUser(); + if(currentUser.isBoardAdmin()) { + return { $set: { backgroundImageURL } }; + } else if (currentUser.isAdmin()) { + return { $set: { backgroundImageURL } }; + } else { + return false; } - return false; }, - async setVisibility(visibility) { - return await Boards.updateAsync(this._id, { $set: { permission: visibility } }); + setVisibility(visibility) { + return { $set: { permission: visibility } }; }, - async addLabel(name, color) { + addLabel(name, color) { + // If label with the same name and color already exists we don't want to + // create another one because they would be indistinguishable in the UI + // (they would still have different `_id` but that is not exposed to the + // user). if (!this.getLabel(name, color)) { const _id = Random.id(6); - return await Boards.updateAsync(this._id, { $push: { labels: { _id, name, color } } }); + return { $push: { labels: { _id, name, color } } }; } - return null; + return {}; }, - async editLabel(labelId, name, color) { + editLabel(labelId, name, color) { if (!this.getLabel(name, color)) { const labelIndex = this.labelIndex(labelId); - return await Boards.updateAsync(this._id, { + return { $set: { [`labels.${labelIndex}.name`]: name, [`labels.${labelIndex}.color`]: color, }, - }); + }; } - return null; + return {}; }, - async removeLabel(labelId) { - return await Boards.updateAsync(this._id, { $pull: { labels: { _id: labelId } } }); + removeLabel(labelId) { + return { $pull: { labels: { _id: labelId } } }; }, - async changeOwnership(fromId, toId) { + changeOwnership(fromId, toId) { const memberIndex = this.memberIndex(fromId); - return await Boards.updateAsync(this._id, { - $set: { [`members.${memberIndex}.userId`]: toId }, - }); + return { + $set: { + [`members.${memberIndex}.userId`]: toId, + }, + }; }, - async addMember(memberId) { + addMember(memberId) { const memberIndex = this.memberIndex(memberId); if (memberIndex >= 0) { - return await Boards.updateAsync(this._id, { - $set: { [`members.${memberIndex}.isActive`]: true }, - }); + return { + $set: { + [`members.${memberIndex}.isActive`]: true, + }, + }; } - return await Boards.updateAsync(this._id, { + return { $push: { members: { userId: memberId, @@ -1522,198 +1416,188 @@ Boards.helpers({ isNoComments: false, isCommentOnly: false, isWorker: false, - isNormalAssignedOnly: false, - isCommentAssignedOnly: false, - isReadOnly: false, - isReadAssignedOnly: false, }, }, - }); + }; }, - async removeMember(memberId) { + removeMember(memberId) { const memberIndex = this.memberIndex(memberId); + + // we do not allow the only one admin to be removed const allowRemove = !this.members[memberIndex].isAdmin || this.activeAdmins().length > 1; if (!allowRemove) { - return await Boards.updateAsync(this._id, { - $set: { [`members.${memberIndex}.isActive`]: true }, - }); + return { + $set: { + [`members.${memberIndex}.isActive`]: true, + }, + }; } - return await Boards.updateAsync(this._id, { + return { $set: { [`members.${memberIndex}.isActive`]: false, [`members.${memberIndex}.isAdmin`]: false, }, - }); + }; }, - async setMemberPermission( + setMemberPermission( memberId, isAdmin, isNoComments, isCommentOnly, isWorker, - isNormalAssignedOnly = false, - isCommentAssignedOnly = false, - isReadOnly = false, - isReadAssignedOnly = false, currentUserId = Meteor.userId(), ) { const memberIndex = this.memberIndex(memberId); + // do not allow change permission of self if (memberId === currentUserId) { isAdmin = this.members[memberIndex].isAdmin; } - return await Boards.updateAsync(this._id, { + return { $set: { [`members.${memberIndex}.isAdmin`]: isAdmin, [`members.${memberIndex}.isNoComments`]: isNoComments, [`members.${memberIndex}.isCommentOnly`]: isCommentOnly, [`members.${memberIndex}.isWorker`]: isWorker, - [`members.${memberIndex}.isNormalAssignedOnly`]: isNormalAssignedOnly, - [`members.${memberIndex}.isCommentAssignedOnly`]: isCommentAssignedOnly, - [`members.${memberIndex}.isReadOnly`]: isReadOnly, - [`members.${memberIndex}.isReadAssignedOnly`]: isReadAssignedOnly, }, - }); + }; }, - async setAllowsSubtasks(allowsSubtasks) { - return await Boards.updateAsync(this._id, { $set: { allowsSubtasks } }); + setAllowsSubtasks(allowsSubtasks) { + return { $set: { allowsSubtasks } }; }, - async setAllowsCreator(allowsCreator) { - return await Boards.updateAsync(this._id, { $set: { allowsCreator } }); + setAllowsCreator(allowsCreator) { + return { $set: { allowsCreator } }; }, - async setAllowsCreatorOnMinicard(allowsCreatorOnMinicard) { - return await Boards.updateAsync(this._id, { $set: { allowsCreatorOnMinicard } }); + setAllowsCreatorOnMinicard(allowsCreatorOnMinicard) { + return { $set: { allowsCreatorOnMinicard } }; }, - async setAllowsMembers(allowsMembers) { - return await Boards.updateAsync(this._id, { $set: { allowsMembers } }); + setAllowsMembers(allowsMembers) { + return { $set: { allowsMembers } }; }, - async setAllowsChecklists(allowsChecklists) { - return await Boards.updateAsync(this._id, { $set: { allowsChecklists } }); + setAllowsChecklists(allowsChecklists) { + return { $set: { allowsChecklists } }; }, - async setAllowsAssignee(allowsAssignee) { - return await Boards.updateAsync(this._id, { $set: { allowsAssignee } }); + setAllowsAssignee(allowsAssignee) { + return { $set: { allowsAssignee } }; }, - async setAllowsAssignedBy(allowsAssignedBy) { - return await Boards.updateAsync(this._id, { $set: { allowsAssignedBy } }); + setAllowsAssignedBy(allowsAssignedBy) { + return { $set: { allowsAssignedBy } }; }, - async setAllowsShowListsOnMinicard(allowsShowListsOnMinicard) { - return await Boards.updateAsync(this._id, { $set: { allowsShowListsOnMinicard } }); + setAllowsShowListsOnMinicard(allowsShowListsOnMinicard) { + return { $set: { allowsShowListsOnMinicard } }; }, - async setAllowsChecklistAtMinicard(allowsChecklistAtMinicard) { - return await Boards.updateAsync(this._id, { $set: { allowsChecklistAtMinicard } }); + setAllowsRequestedBy(allowsRequestedBy) { + return { $set: { allowsRequestedBy } }; }, - async setAllowsRequestedBy(allowsRequestedBy) { - return await Boards.updateAsync(this._id, { $set: { allowsRequestedBy } }); + setAllowsCardSortingByNumber(allowsCardSortingByNumber) { + return { $set: { allowsCardSortingByNumber } }; }, - async setAllowsCardSortingByNumber(allowsCardSortingByNumber) { - return await Boards.updateAsync(this._id, { $set: { allowsCardSortingByNumber } }); + setAllowsShowLists(allowsShowLists) { + return { $set: { allowsShowLists } }; }, - async setAllowsShowLists(allowsShowLists) { - return await Boards.updateAsync(this._id, { $set: { allowsShowLists } }); + + setAllowsAttachments(allowsAttachments) { + return { $set: { allowsAttachments } }; }, - async setAllowsAttachments(allowsAttachments) { - return await Boards.updateAsync(this._id, { $set: { allowsAttachments } }); + setAllowsLabels(allowsLabels) { + return { $set: { allowsLabels } }; }, - async setAllowsLabels(allowsLabels) { - return await Boards.updateAsync(this._id, { $set: { allowsLabels } }); + setAllowsComments(allowsComments) { + return { $set: { allowsComments } }; }, - async setAllowsComments(allowsComments) { - return await Boards.updateAsync(this._id, { $set: { allowsComments } }); + setAllowsDescriptionTitle(allowsDescriptionTitle) { + return { $set: { allowsDescriptionTitle } }; }, - async setAllowsDescriptionTitle(allowsDescriptionTitle) { - return await Boards.updateAsync(this._id, { $set: { allowsDescriptionTitle } }); + setAllowsCardNumber(allowsCardNumber) { + return { $set: { allowsCardNumber } }; }, - async setAllowsCardNumber(allowsCardNumber) { - return await Boards.updateAsync(this._id, { $set: { allowsCardNumber } }); + setAllowsDescriptionText(allowsDescriptionText) { + return { $set: { allowsDescriptionText } }; }, - async setAllowsDescriptionText(allowsDescriptionText) { - return await Boards.updateAsync(this._id, { $set: { allowsDescriptionText } }); + setallowsDescriptionTextOnMinicard(allowsDescriptionTextOnMinicard) { + return { $set: { allowsDescriptionTextOnMinicard } }; }, - async setallowsDescriptionTextOnMinicard(allowsDescriptionTextOnMinicard) { - return await Boards.updateAsync(this._id, { $set: { allowsDescriptionTextOnMinicard } }); + setallowsCoverAttachmentOnMinicard(allowsCoverAttachmentOnMinicard) { + return { $set: { allowsCoverAttachmentOnMinicard } }; }, - async setallowsCoverAttachmentOnMinicard(allowsCoverAttachmentOnMinicard) { - return await Boards.updateAsync(this._id, { $set: { allowsCoverAttachmentOnMinicard } }); + setallowsBadgeAttachmentOnMinicard(allowsBadgeAttachmentOnMinicard) { + return { $set: { allowsBadgeAttachmentOnMinicard } }; }, - async setallowsBadgeAttachmentOnMinicard(allowsBadgeAttachmentOnMinicard) { - return await Boards.updateAsync(this._id, { $set: { allowsBadgeAttachmentOnMinicard } }); + setallowsCardSortingByNumberOnMinicard(allowsCardSortingByNumberOnMinicard) { + return { $set: { allowsCardSortingByNumberOnMinicard } }; }, - async setallowsCardSortingByNumberOnMinicard(allowsCardSortingByNumberOnMinicard) { - return await Boards.updateAsync(this._id, { $set: { allowsCardSortingByNumberOnMinicard } }); + setAllowsActivities(allowsActivities) { + return { $set: { allowsActivities } }; }, - async setAllowsActivities(allowsActivities) { - return await Boards.updateAsync(this._id, { $set: { allowsActivities } }); + setAllowsReceivedDate(allowsReceivedDate) { + return { $set: { allowsReceivedDate } }; }, - async setAllowsReceivedDate(allowsReceivedDate) { - return await Boards.updateAsync(this._id, { $set: { allowsReceivedDate } }); + setAllowsCardCounterList(allowsCardCounterList) { + return { $set: { allowsCardCounterList } }; }, - async setAllowsCardCounterList(allowsCardCounterList) { - return await Boards.updateAsync(this._id, { $set: { allowsCardCounterList } }); + setAllowsBoardMemberList(allowsBoardMemberList) { + return { $set: { allowsBoardMemberList } }; }, - async setAllowsBoardMemberList(allowsBoardMemberList) { - return await Boards.updateAsync(this._id, { $set: { allowsBoardMemberList } }); + setAllowsStartDate(allowsStartDate) { + return { $set: { allowsStartDate } }; }, - async setAllowsStartDate(allowsStartDate) { - return await Boards.updateAsync(this._id, { $set: { allowsStartDate } }); + setAllowsEndDate(allowsEndDate) { + return { $set: { allowsEndDate } }; }, - async setAllowsEndDate(allowsEndDate) { - return await Boards.updateAsync(this._id, { $set: { allowsEndDate } }); + setAllowsDueDate(allowsDueDate) { + return { $set: { allowsDueDate } }; }, - async setAllowsDueDate(allowsDueDate) { - return await Boards.updateAsync(this._id, { $set: { allowsDueDate } }); + setSubtasksDefaultBoardId(subtasksDefaultBoardId) { + return { $set: { subtasksDefaultBoardId } }; }, - async setSubtasksDefaultBoardId(subtasksDefaultBoardId) { - return await Boards.updateAsync(this._id, { $set: { subtasksDefaultBoardId } }); + setSubtasksDefaultListId(subtasksDefaultListId) { + return { $set: { subtasksDefaultListId } }; }, - async setSubtasksDefaultListId(subtasksDefaultListId) { - return await Boards.updateAsync(this._id, { $set: { subtasksDefaultListId } }); + setPresentParentTask(presentParentTask) { + return { $set: { presentParentTask } }; }, - async setPresentParentTask(presentParentTask) { - return await Boards.updateAsync(this._id, { $set: { presentParentTask } }); + move(sortIndex) { + return { $set: { sort: sortIndex } }; }, - async move(sortIndex) { - return await Boards.updateAsync(this._id, { $set: { sort: sortIndex } }); - }, - - async toggleShowActivities() { - return await Boards.updateAsync(this._id, { $set: { showActivities: !this.showActivities } }); + toggleShowActivities() { + return { $set: { showActivities: !this.showActivities } }; }, }); @@ -1725,32 +1609,31 @@ function boardRemover(userId, doc) { ); } -Boards.uniqueTitle = async title => { +Boards.uniqueTitle = title => { const m = title.match( new RegExp('^(?<title>.*?)\\s*(\\[(?<num>\\d+)]\\s*$|\\s*$)'), ); const base = escapeForRegex(m.groups.title); const baseTitle = m.groups.title; - const boards = await ReactiveCache.getBoards({ title: new RegExp(`^${base}\\s*(\\[(?<num>\\d+)]\\s*$|\\s*$)`) }); + boards = ReactiveCache.getBoards({ title: new RegExp(`^${base}\\s*(\\[(?<num>\\d+)]\\s*$|\\s*$)`) }); if (boards.length > 0) { let num = 0; - const numberedBoards = await ReactiveCache.getBoards({ title: new RegExp(`^${base}\\s*\\[\\d+]\\s*$`) }); - for (const board of numberedBoards) { - const m = board.title.match( - new RegExp('^(?<title>.*?)\\s*\\[(?<num>\\d+)]\\s*$'), - ); - if (m) { - const n = parseInt(m.groups.num, 10); - num = num < n ? n : num; - } - } + ReactiveCache.getBoards({ title: new RegExp(`^${base}\\s*\\[\\d+]\\s*$`) }).forEach( + board => { + const m = board.title.match( + new RegExp('^(?<title>.*?)\\s*\\[(?<num>\\d+)]\\s*$'), + ); + if (m) { + const n = parseInt(m.groups.num, 10); + num = num < n ? n : num; + } + }, + ); return `${baseTitle} [${num + 1}]`; } return title; }; -// Non-async: returns data on client, Promise on server. -// Server callers must await. Boards.userSearch = ( userId, selector = {}, @@ -1765,51 +1648,42 @@ Boards.userSearch = ( if (userId) { selector.$or.push({ members: { $elemMatch: { userId, isActive: true } } }); } - return ReactiveCache.getBoards(selector, projection); + const ret = ReactiveCache.getBoards(selector, projection); + return ret; }; -// Non-async: returns data on client (for Blaze templates), Promise on server. -// Server callers must await. Boards.userBoards = ( userId, archived = false, selector = {}, projection = {}, ) => { - const _buildSelector = (user) => { - if (!user) return null; - if (typeof archived === 'boolean') { - selector.archived = archived; - } - if (!selector.type) { - selector.type = 'board'; - } - selector.$or = [ - { permission: 'public' }, - { members: { $elemMatch: { userId, isActive: true } } }, - { orgs: { $elemMatch: { orgId: { $in: user.orgIds() }, isActive: true } } }, - { teams: { $elemMatch: { teamId: { $in: user.teamIds() }, isActive: true } } }, - ]; - return selector; - }; - - if (Meteor.isServer) { - return (async () => { - const user = await ReactiveCache.getUser(userId); - if (!_buildSelector(user)) return []; - return await ReactiveCache.getBoards(selector, projection); - })(); - } const user = ReactiveCache.getUser(userId); - if (!_buildSelector(user)) return []; + if (!user) { + return []; + } + + if (typeof archived === 'boolean') { + selector.archived = archived; + } + if (!selector.type) { + selector.type = 'board'; + } + + selector.$or = [ + { permission: 'public' }, + { members: { $elemMatch: { userId, isActive: true } } }, + { orgs: { $elemMatch: { orgId: { $in: user.orgIds() }, isActive: true } } }, + { teams: { $elemMatch: { teamId: { $in: user.teamIds() }, isActive: true } } }, + ]; + return ReactiveCache.getBoards(selector, projection); }; -Boards.userBoardIds = async (userId, archived = false, selector = {}) => { - const boards = await Boards.userBoards(userId, archived, selector, { +Boards.userBoardIds = (userId, archived = false, selector = {}) => { + return Boards.userBoards(userId, archived, selector, { fields: { _id: 1 }, - }); - return boards.map(board => { + }).map(board => { return board._id; }); }; @@ -1828,18 +1702,7 @@ Boards.labelColors = () => { if (Meteor.isServer) { Boards.allow({ - insert(userId, doc) { - // Check if user is logged in - if (!userId) return false; - - // If allowPrivateOnly is enabled, only allow private boards - const allowPrivateOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly')?.booleanValue; - if (allowPrivateOnly && doc.permission === 'public') { - return false; - } - - return true; - }, + insert: Meteor.userId, update: allowIsBoardAdmin, remove: allowIsBoardAdmin, fetch: ['members'], @@ -1889,41 +1752,26 @@ if (Meteor.isServer) { fetch: ['members'], }); - // Deny changing permission to public if allowPrivateOnly is enabled - Boards.deny({ - update(userId, doc, fieldNames, modifier) { - if (!_.contains(fieldNames, 'permission')) return false; - - const allowPrivateOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly')?.booleanValue; - if (allowPrivateOnly && modifier.$set && modifier.$set.permission === 'public') { - return true; - } - - return false; - }, - fetch: [], - }); - Meteor.methods({ - async getBackgroundImageURL(boardId) { + getBackgroundImageURL(boardId) { check(boardId, String); - return await ReactiveCache.getBoard(boardId, {}, { backgroundImageUrl: 1 }); + return ReactiveCache.getBoard(boardId, {}, { backgroundImageUrl: 1 }); }, - async quitBoard(boardId) { + quitBoard(boardId) { check(boardId, String); - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (board) { const userId = Meteor.userId(); const index = board.memberIndex(userId); if (index >= 0) { - await board.removeMember(userId); + board.removeMember(userId); return true; } else throw new Meteor.Error('error-board-notAMember'); } else throw new Meteor.Error('error-board-doesNotExist'); }, - async acceptInvite(boardId) { + acceptInvite(boardId) { check(boardId, String); - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (!board) { throw new Meteor.Error('error-board-doesNotExist'); } @@ -1933,22 +1781,10 @@ if (Meteor.isServer) { 'profile.invitedBoards': boardId, }, }); - - // Ensure the user is active on the board - Boards.update({ - _id: boardId, - 'members.userId': Meteor.userId() - }, { - $set: { - 'members.$.isActive': true, - modifiedAt: new Date() - } - }); }, - async myLabelNames() { + myLabelNames() { let names = []; - const boards = await Boards.userBoards(Meteor.userId()); - for (const board of boards) { + Boards.userBoards(Meteor.userId()).forEach(board => { // Only return labels when they exist. if (board.labels !== undefined) { names = names.concat( @@ -1958,21 +1794,21 @@ if (Meteor.isServer) { return label.name; }), ); + } else { + return []; } - } + }); return _.uniq(names).sort(); }, - async myBoardNames() { - const boards = await Boards.userBoards(Meteor.userId()); + myBoardNames() { return _.uniq( - boards.map(board => { + Boards.userBoards(Meteor.userId()).map(board => { return board.title; }), ).sort(); }, - async setAllBoardsHideActivities() { - const currentUser = await ReactiveCache.getCurrentUser(); - if ((currentUser || {}).isAdmin) { + setAllBoardsHideActivities() { + if ((ReactiveCache.getCurrentUser() || {}).isAdmin) { Boards.update( { showActivities: true @@ -1994,78 +1830,31 @@ if (Meteor.isServer) { }); Meteor.methods({ - async archiveBoard(boardId) { + archiveBoard(boardId) { check(boardId, String); - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (board) { const userId = Meteor.userId(); const index = board.memberIndex(userId); if (index >= 0) { - await board.archive(); + board.archive(); return true; } else throw new Meteor.Error('error-board-notAMember'); } else throw new Meteor.Error('error-board-doesNotExist'); }, - async setBoardOrgs(boardOrgsArray, currBoardId){ + setBoardOrgs(boardOrgsArray, currBoardId){ check(boardOrgsArray, Array); check(currBoardId, String); - const userId = Meteor.userId(); - if (!userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in to perform this action.'); - } - const board = await ReactiveCache.getBoard(currBoardId); - if (!board) { - throw new Meteor.Error('board-not-found', 'Board not found.'); - } - if (!allowIsBoardAdmin(userId, board)) { - throw new Meteor.Error('not-authorized', 'You must be a board admin to perform this action.'); - } - // Validate boardOrgsArray - for (const org of boardOrgsArray) { - check(org.orgId, String); - check(org.orgDisplayName, String); - check(org.isActive, Boolean); - } Boards.update(currBoardId, { $set: { orgs: boardOrgsArray, }, }); }, - async setBoardTeams(boardTeamsArray, membersArray, currBoardId){ + setBoardTeams(boardTeamsArray, membersArray, currBoardId){ check(boardTeamsArray, Array); check(membersArray, Array); check(currBoardId, String); - const userId = Meteor.userId(); - if (!userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in to perform this action.'); - } - const board = await ReactiveCache.getBoard(currBoardId); - if (!board) { - throw new Meteor.Error('board-not-found', 'Board not found.'); - } - if (!allowIsBoardAdmin(userId, board)) { - throw new Meteor.Error('not-authorized', 'You must be a board admin to perform this action.'); - } - // Validate boardTeamsArray - for (const team of boardTeamsArray) { - check(team.teamId, String); - check(team.teamDisplayName, String); - check(team.isActive, Boolean); - } - // Validate membersArray - for (const member of membersArray) { - check(member.userId, String); - check(member.isAdmin, Boolean); - check(member.isActive, Boolean); - if (member.isNoComments !== undefined) check(member.isNoComments, Boolean); - if (member.isCommentOnly !== undefined) check(member.isCommentOnly, Boolean); - if (member.isWorker !== undefined) check(member.isWorker, Boolean); - if (member.isNormalAssignedOnly !== undefined) check(member.isNormalAssignedOnly, Boolean); - if (member.isCommentAssignedOnly !== undefined) check(member.isCommentAssignedOnly, Boolean); - if (member.isReadOnly !== undefined) check(member.isReadOnly, Boolean); - if (member.isReadAssignedOnly !== undefined) check(member.isReadAssignedOnly, Boolean); - } Boards.update(currBoardId, { $set: { members: membersArray, @@ -2077,8 +1866,8 @@ if (Meteor.isServer) { } // Insert new board at last position in sort order. -Boards.before.insert(async (userId, doc) => { - const lastBoard = await ReactiveCache.getBoard( +Boards.before.insert((userId, doc) => { + const lastBoard = ReactiveCache.getBoard( { sort: { $exists: true } }, { sort: { sort: -1 } }, ); @@ -2089,16 +1878,16 @@ Boards.before.insert(async (userId, doc) => { if (Meteor.isServer) { // Let MongoDB ensure that a member is not included twice in the same board - Meteor.startup(async () => { - await Boards._collection.createIndexAsync({ modifiedAt: -1 }); - await Boards._collection.createIndexAsync( + Meteor.startup(() => { + Boards._collection.createIndex({ modifiedAt: -1 }); + Boards._collection.createIndex( { _id: 1, 'members.userId': 1, }, { unique: true }, ); - await Boards._collection.createIndexAsync({ 'members.userId': 1 }); + Boards._collection.createIndex({ 'members.userId': 1 }); }); // Genesis: the first activity of the newly created board @@ -2160,7 +1949,7 @@ if (Meteor.isServer) { } if (modifier.$set) { const boardId = doc._id; - foreachRemovedMember(doc, modifier.$set, async memberId => { + foreachRemovedMember(doc, modifier.$set, memberId => { Cards.update( { boardId }, { @@ -2183,7 +1972,7 @@ if (Meteor.isServer) { ); const board = Boards._transform(doc); - await board.setWatcher(memberId, false); + board.setWatcher(memberId, false); // Remove board from users starred list if (!board.isPublic()) { @@ -2262,7 +2051,7 @@ if (Meteor.isServer) { * @return_type [{_id: string, * title: string}] */ - JsonRoutes.add('GET', '/api/users/:userId/boards', async function(req, res) { + JsonRoutes.add('GET', '/api/users/:userId/boards', function(req, res) { try { Authentication.checkLoggedIn(req.userId); const paramUserId = req.params.userId; @@ -2273,7 +2062,7 @@ if (Meteor.isServer) { req.userId === paramUserId, ); - const boards = await ReactiveCache.getBoards( + const data = ReactiveCache.getBoards( { archived: false, 'members.userId': paramUserId, @@ -2281,8 +2070,7 @@ if (Meteor.isServer) { { sort: { sort: 1 /* boards default sorting */ }, }, - ); - const data = boards.map(function(board) { + ).map(function(board) { return { _id: board._id, title: board.title, @@ -2305,18 +2093,17 @@ if (Meteor.isServer) { * @return_type [{_id: string, title: string}] */ - JsonRoutes.add('GET', '/api/boards', async function(req, res) { + JsonRoutes.add('GET', '/api/boards', function(req, res) { try { Authentication.checkUserId(req.userId); - const boards = await ReactiveCache.getBoards( - { permission: 'public' }, - { - sort: { sort: 1 /* boards default sorting */ }, - }, - ); JsonRoutes.sendResult(res, { code: 200, - data: boards.map(function(doc) { + data: ReactiveCache.getBoards( + { permission: 'public' }, + { + sort: { sort: 1 /* boards default sorting */ }, + }, + ).map(function(doc) { return { _id: doc._id, title: doc.title, @@ -2337,16 +2124,14 @@ if (Meteor.isServer) { * * @return_type {private: integer, public: integer} */ - JsonRoutes.add('GET', '/api/boards_count', async function(req, res) { + JsonRoutes.add('GET', '/api/boards_count', function(req, res) { try { Authentication.checkUserId(req.userId); - const privateBoards = await ReactiveCache.getBoards({ permission: 'private' }); - const publicBoards = await ReactiveCache.getBoards({ permission: 'public' }); JsonRoutes.sendResult(res, { code: 200, data: { - private: privateBoards.length, - public: publicBoards.length, + private: ReactiveCache.getBoards({ permission: 'private' }).length, + public: ReactiveCache.getBoards({ permission: 'public' }).length, }, }); } catch (error) { @@ -2364,15 +2149,14 @@ if (Meteor.isServer) { * @param {string} boardId the ID of the board to retrieve the data * @return_type Boards */ - JsonRoutes.add('GET', '/api/boards/:boardId', async function(req, res) { + JsonRoutes.add('GET', '/api/boards/:boardId', function(req, res) { try { const paramBoardId = req.params.boardId; Authentication.checkBoardAccess(req.userId, paramBoardId); - const board = await ReactiveCache.getBoard(paramBoardId); JsonRoutes.sendResult(res, { code: 200, - data: board, + data: ReactiveCache.getBoard(paramBoardId), }); } catch (error) { JsonRoutes.sendResult(res, { @@ -2412,8 +2196,6 @@ if (Meteor.isServer) { JsonRoutes.add('POST', '/api/boards', function(req, res) { try { Authentication.checkLoggedIn(req.userId); - const allowPrivateOnly = TableVisibilityModeSettings.findOne('tableVisibilityMode-allowPrivateOnly')?.booleanValue; - const permission = allowPrivateOnly ? 'private' : (req.body.permission || 'private'); const id = Boards.insert({ title: req.body.title, members: [ @@ -2426,7 +2208,7 @@ if (Meteor.isServer) { isWorker: req.body.isWorker || false, }, ], - permission, + permission: req.body.permission || 'private', color: req.body.color || 'belize', migrationVersion: 1, // Latest version - no migration needed }); @@ -2483,8 +2265,8 @@ if (Meteor.isServer) { */ JsonRoutes.add('PUT', '/api/boards/:boardId/title', function(req, res) { try { + Authentication.checkUserId(req.userId); const boardId = req.params.boardId; - Authentication.checkBoardWriteAccess(req.userId, boardId); const title = req.body.title; Boards.direct.update({ _id: boardId }, { $set: { title } }); @@ -2517,12 +2299,12 @@ if (Meteor.isServer) { * * @return_type string */ - JsonRoutes.add('PUT', '/api/boards/:boardId/labels', async function(req, res) { + JsonRoutes.add('PUT', '/api/boards/:boardId/labels', function(req, res) { const id = req.params.boardId; - Authentication.checkBoardWriteAccess(req.userId, id); + Authentication.checkBoardAccess(req.userId, id); try { if (req.body.hasOwnProperty('label')) { - const board = await ReactiveCache.getBoard(id); + const board = ReactiveCache.getBoard(id); const color = req.body.label.color; const name = req.body.label.name; const labelId = Random.id(6); @@ -2560,14 +2342,14 @@ if (Meteor.isServer) { * * @return_type string */ -JsonRoutes.add('POST', '/api/boards/:boardId/copy', async function(req, res) { +JsonRoutes.add('POST', '/api/boards/:boardId/copy', function(req, res) { const id = req.params.boardId; - const board = await ReactiveCache.getBoard(id); + const board = ReactiveCache.getBoard(id); const adminAccess = board.members.some(e => e.userId === req.userId && e.isAdmin); Authentication.checkAdminOrCondition(req.userId, adminAccess); try { - board['title'] = req.body.title || await Boards.uniqueTitle(board.title); - ret = await board.copy(); + board['title'] = req.body.title || Boards.uniqueTitle(board.title); + ret = board.copy(); JsonRoutes.sendResult(res, { code: 200, data: ret, @@ -2590,12 +2372,8 @@ JsonRoutes.add('POST', '/api/boards/:boardId/copy', async function(req, res) { * @param {boolean} isNoComments NoComments capability * @param {boolean} isCommentOnly CommentsOnly capability * @param {boolean} isWorker Worker capability - * @param {boolean} isNormalAssignedOnly NormalAssignedOnly capability - * @param {boolean} isCommentAssignedOnly CommentAssignedOnly capability - * @param {boolean} isReadOnly ReadOnly capability - * @param {boolean} isReadAssignedOnly ReadAssignedOnly capability */ - JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', async function( + JsonRoutes.add('POST', '/api/boards/:boardId/members/:memberId', function( req, res, ) { @@ -2603,8 +2381,8 @@ JsonRoutes.add('POST', '/api/boards/:boardId/copy', async function(req, res) { Authentication.checkUserId(req.userId); const boardId = req.params.boardId; const memberId = req.params.memberId; - const { isAdmin, isNoComments, isCommentOnly, isWorker, isNormalAssignedOnly, isCommentAssignedOnly, isReadOnly, isReadAssignedOnly } = req.body; - const board = await ReactiveCache.getBoard(boardId); + const { isAdmin, isNoComments, isCommentOnly, isWorker } = req.body; + const board = ReactiveCache.getBoard(boardId); function isTrue(data) { try { return data.toLowerCase() === 'true'; @@ -2612,16 +2390,12 @@ JsonRoutes.add('POST', '/api/boards/:boardId/copy', async function(req, res) { return data; } } - const query = await board.setMemberPermission( + const query = board.setMemberPermission( memberId, isTrue(isAdmin), isTrue(isNoComments), isTrue(isCommentOnly), isTrue(isWorker), - isTrue(isNormalAssignedOnly), - isTrue(isCommentAssignedOnly), - isTrue(isReadOnly), - isTrue(isReadAssignedOnly), req.userId, ); @@ -2654,14 +2428,13 @@ JsonRoutes.add('POST', '/api/boards/:boardId/copy', async function(req, res) { * cardId: string * }] */ - JsonRoutes.add('GET', '/api/boards/:boardId/attachments', async function(req, res) { + JsonRoutes.add('GET', '/api/boards/:boardId/attachments', function(req, res) { const paramBoardId = req.params.boardId; Authentication.checkBoardAccess(req.userId, paramBoardId); - const attachments = await ReactiveCache - .getAttachments({'meta.boardId': paramBoardId }, {}, true); JsonRoutes.sendResult(res, { code: 200, - data: attachments + data: ReactiveCache + .getAttachments({'meta.boardId': paramBoardId }, {}, true) .each() .map(function(attachment) { return { diff --git a/models/cardCommentReactions.js b/models/cardCommentReactions.js index 8271a563f..5e9e23251 100644 --- a/models/cardCommentReactions.js +++ b/models/cardCommentReactions.js @@ -51,20 +51,20 @@ CardCommentReactions.attachSchema( CardCommentReactions.allow({ insert(userId, doc) { - return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId)); }, update(userId, doc) { - return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId)); }, remove(userId, doc) { - return allowIsBoardMember(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId)); }, fetch: ['boardId'], }); if (Meteor.isServer) { - Meteor.startup(async () => { - await CardCommentReactions._collection.createIndexAsync({ cardCommentId: 1 }, { unique: true }); + Meteor.startup(() => { + CardCommentReactions._collection.createIndex({ cardCommentId: 1 }, { unique: true }); }); } diff --git a/models/cardComments.js b/models/cardComments.js index a377083ed..b3c3a969e 100644 --- a/models/cardComments.js +++ b/models/cardComments.js @@ -82,14 +82,13 @@ CardComments.attachSchema( CardComments.allow({ insert(userId, doc) { - // ReadOnly users cannot add comments. Only members who can comment are allowed. - return allowIsBoardMemberCommentOnly(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId)); }, update(userId, doc) { - return userId === doc.userId || allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + return userId === doc.userId || allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId)); }, remove(userId, doc) { - return userId === doc.userId || allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + return userId === doc.userId || allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId)); }, fetch: ['userId', 'boardId'], }); @@ -154,8 +153,8 @@ CardComments.helpers({ CardComments.hookOptions.after.update = { fetchPrevious: false }; -async function commentCreation(userId, doc) { - const card = await ReactiveCache.getCard(doc.cardId); +function commentCreation(userId, doc) { + const card = ReactiveCache.getCard(doc.cardId); Activities.insert({ userId, activityType: 'addComment', @@ -167,9 +166,9 @@ async function commentCreation(userId, doc) { }); } -CardComments.textSearch = async (userId, textArray) => { +CardComments.textSearch = (userId, textArray) => { const selector = { - boardId: { $in: await Boards.userBoardIds(userId) }, + boardId: { $in: Boards.userBoardIds(userId) }, $and: [], }; @@ -180,7 +179,7 @@ CardComments.textSearch = async (userId, textArray) => { // eslint-disable-next-line no-console // console.log('cardComments selector:', selector); - const comments = await ReactiveCache.getCardComments(selector); + const comments = ReactiveCache.getCardComments(selector); // eslint-disable-next-line no-console // console.log('count:', comments.count()); // eslint-disable-next-line no-console @@ -192,17 +191,17 @@ CardComments.textSearch = async (userId, textArray) => { if (Meteor.isServer) { // Comments are often fetched within a card, so we create an index to make these // queries more efficient. - Meteor.startup(async () => { - await CardComments._collection.createIndexAsync({ modifiedAt: -1 }); - await CardComments._collection.createIndexAsync({ cardId: 1, createdAt: -1 }); + Meteor.startup(() => { + CardComments._collection.createIndex({ modifiedAt: -1 }); + CardComments._collection.createIndex({ cardId: 1, createdAt: -1 }); }); - CardComments.after.insert(async (userId, doc) => { - await commentCreation(userId, doc); + CardComments.after.insert((userId, doc) => { + commentCreation(userId, doc); }); - CardComments.after.update(async (userId, doc) => { - const card = await ReactiveCache.getCard(doc.cardId); + CardComments.after.update((userId, doc) => { + const card = ReactiveCache.getCard(doc.cardId); Activities.insert({ userId, activityType: 'editComment', @@ -214,8 +213,8 @@ if (Meteor.isServer) { }); }); - CardComments.before.remove(async (userId, doc) => { - const card = await ReactiveCache.getCard(doc.cardId); + CardComments.before.remove((userId, doc) => { + const card = ReactiveCache.getCard(doc.cardId); Activities.insert({ userId, activityType: 'deleteComment', @@ -225,7 +224,7 @@ if (Meteor.isServer) { listId: card.listId, swimlaneId: card.swimlaneId, }); - const activity = await ReactiveCache.getActivity({ commentId: doc._id }); + const activity = ReactiveCache.getActivity({ commentId: doc._id }); if (activity) { Activities.remove(activity._id); } @@ -244,7 +243,7 @@ if (Meteor.isServer) { * comment: string, * authorId: string}] */ - JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments', async function ( + JsonRoutes.add('GET', '/api/boards/:boardId/cards/:cardId/comments', function ( req, res, ) { @@ -254,10 +253,10 @@ if (Meteor.isServer) { Authentication.checkBoardAccess(req.userId, paramBoardId); JsonRoutes.sendResult(res, { code: 200, - data: (await ReactiveCache.getCardComments({ + data: ReactiveCache.getCardComments({ boardId: paramBoardId, cardId: paramCardId, - })).map(function (doc) { + }).map(function (doc) { return { _id: doc._id, comment: doc.text, @@ -285,7 +284,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/cards/:cardId/comments/:commentId', - async function (req, res) { + function (req, res) { try { const paramBoardId = req.params.boardId; const paramCommentId = req.params.commentId; @@ -293,7 +292,7 @@ if (Meteor.isServer) { Authentication.checkBoardAccess(req.userId, paramBoardId); JsonRoutes.sendResult(res, { code: 200, - data: await ReactiveCache.getCardComment({ + data: ReactiveCache.getCardComment({ _id: paramCommentId, cardId: paramCardId, boardId: paramBoardId, @@ -314,19 +313,20 @@ if (Meteor.isServer) { * * @param {string} boardId the board ID of the card * @param {string} cardId the ID of the card - * @param {string} comment the content of the comment + * @param {string} authorId the user who 'posted' the comment + * @param {string} text the content of the comment * @return_type {_id: string} */ JsonRoutes.add( 'POST', '/api/boards/:boardId/cards/:cardId/comments', - async function (req, res) { + function (req, res) { try { const paramBoardId = req.params.boardId; const paramCardId = req.params.cardId; Authentication.checkBoardAccess(req.userId, paramBoardId); const id = CardComments.direct.insert({ - userId: req.userId, + userId: req.body.authorId, text: req.body.comment, cardId: paramCardId, boardId: paramBoardId, @@ -339,12 +339,12 @@ if (Meteor.isServer) { }, }); - const cardComment = await ReactiveCache.getCardComment({ + const cardComment = ReactiveCache.getCardComment({ _id: id, cardId: paramCardId, boardId: paramBoardId, }); - await commentCreation(req.userId, cardComment); + commentCreation(req.body.authorId, cardComment); } catch (error) { JsonRoutes.sendResult(res, { code: 200, diff --git a/models/cards.js b/models/cards.js index 0c6cd9f62..546efdfe6 100644 --- a/models/cards.js +++ b/models/cards.js @@ -1,24 +1,23 @@ import { ReactiveCache, ReactiveMiniMongoIndex } from '/imports/reactiveCache'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; -import { - formatDateTime, - formatDate, - formatTime, - getISOWeek, - isValidDate, - isBefore, - isAfter, - isSame, - add, - subtract, - startOf, - endOf, - format, - parseDate, - now, - createDate, - fromNow, - calendar +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar } from '/imports/lib/dateUtils'; import { ALLOWED_COLORS, @@ -498,17 +497,16 @@ Cards.attachSchema( type: Boolean, defaultValue: false, }, - showListOnMinicard: { + hideFinishedChecklistIfItemsAreHidden: { /** - * show list name on minicard? + * hide completed checklist? */ type: Boolean, optional: true, - defaultValue: false, }, - showChecklistAtMinicard: { + showListOnMinicard: { /** - * show checklist on minicard? + * show list name on minicard? */ type: Boolean, optional: true, @@ -518,7 +516,7 @@ Cards.attachSchema( ); // Centralized update policy for Cards -// Security: deny any direct client updates to 'vote' fields; require write access otherwise +// Security: deny any direct client updates to 'vote' fields; require membership otherwise canUpdateCard = function(userId, doc, fields) { if (!userId) return false; const fieldNames = fields || []; @@ -530,21 +528,19 @@ canUpdateCard = function(userId, doc, fields) { if (_.some(fieldNames, f => typeof f === 'string' && (f === 'poker' || f.indexOf('poker.') === 0))) { return false; } - // ReadOnly users cannot edit cards - return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId)); }; Cards.allow({ insert(userId, doc) { - // ReadOnly users cannot create cards - return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId)); }, + update(userId, doc, fields) { return canUpdateCard(userId, doc, fields); }, remove(userId, doc) { - // ReadOnly users cannot delete cards - return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMember(userId, ReactiveCache.getBoard(doc.boardId)); }, fetch: ['boardId'], }); @@ -572,20 +568,14 @@ Cards.helpers({ }, mapCustomFieldsToBoard(boardId) { - // Guard against undefined/null customFields - if (!this.customFields || !Array.isArray(this.customFields)) { - return []; - } // Map custom fields to new board - const result = []; - for (const cf of this.customFields) { + return this.customFields.map(cf => { const oldCf = ReactiveCache.getCustomField(cf._id); // Check if oldCf is undefined or null if (!oldCf) { //console.error(`Custom field with ID ${cf._id} not found.`); - result.push(cf); // Skip this field if oldCf is not found - continue; + return cf; // Skip this field if oldCf is not found } const newCf = ReactiveCache.getCustomField({ @@ -600,9 +590,8 @@ Cards.helpers({ oldCf.addBoard(boardId); } - result.push(cf); - } - return result; + return cf; + }); }, @@ -610,15 +599,6 @@ Cards.helpers({ const oldId = this._id; const oldCard = ReactiveCache.getCard(oldId); - // Work on a shallow copy to avoid mutating the source card in ReactiveCache - const cardData = { ...this }; - delete cardData._id; - - // Normalize customFields to ensure it's always an array - if (!Array.isArray(cardData.customFields)) { - cardData.customFields = []; - } - // we must only copy the labels and custom fields if the target board // differs from the source board if (this.boardId !== boardId) { @@ -641,18 +621,19 @@ Cards.helpers({ }), '_id', ); - cardData.labelIds = newCardLabels; + // now set the new label ids + delete this.labelIds; + this.labelIds = newCardLabels; this.customFields = this.mapCustomFieldsToBoard(newBoard._id); } delete this._id; this.boardId = boardId; - const board = ReactiveCache.getBoard(boardId); - this.cardNumber = board.getNextCardNumber(); + this.cardNumber = ReactiveCache.getBoard(boardId).getNextCardNumber(); this.swimlaneId = swimlaneId; this.listId = listId; - const _id = Cards.insertAsync(this); + const _id = Cards.insert(this); // Copy attachments oldCard.attachments() @@ -661,24 +642,21 @@ Cards.helpers({ }); // copy checklists - const checklists = ReactiveCache.getChecklists({ cardId: oldId }); - for (const ch of checklists) { + ReactiveCache.getChecklists({ cardId: oldId }).forEach(ch => { ch.copy(_id); - } + }); // copy subtasks - const subtasks = ReactiveCache.getCards({ parentId: oldId }); - for (const subtask of subtasks) { + ReactiveCache.getCards({ parentId: oldId }).forEach(subtask => { subtask.parentId = _id; subtask._id = null; - Cards.insertAsync(subtask); - } + Cards.insert(subtask); + }); // copy card comments - const comments = ReactiveCache.getCardComments({ cardId: oldId }); - for (const cmt of comments) { + ReactiveCache.getCardComments({ cardId: oldId }).forEach(cmt => { cmt.copy(_id); - } + }); // restore the id, otherwise new copies will fail this._id = oldId; @@ -1036,24 +1014,18 @@ Cards.helpers({ absoluteUrl() { const board = this.board(); - if (!board) return undefined; return FlowRouter.url('card', { boardId: board._id, - slug: board.slug || 'board', + slug: board.slug, cardId: this._id, - swimlaneId: this.swimlaneId, - listId: this.listId, }); }, originRelativeUrl() { const board = this.board(); - if (!board) return undefined; return FlowRouter.path('card', { boardId: board._id, - slug: board.slug || 'board', + slug: board.slug, cardId: this._id, - swimlaneId: this.swimlaneId, - listId: this.listId, }); }, @@ -1129,7 +1101,7 @@ Cards.helpers({ }, parentString(sep) { - return (this.parentList()) + return this.parentList() .map(function(elem) { return elem.title; }) @@ -1230,7 +1202,7 @@ Cards.helpers({ const board = ReactiveCache.getBoard(this.linkedId); ret = board.addMember(memberId); } else { - ret = Cards.updateAsync( + ret = Cards.update( { _id: this.getRealId() }, { $addToSet: { members: memberId } }, ); @@ -1240,7 +1212,7 @@ Cards.helpers({ assignAssignee(assigneeId) { if (this.isLinkedCard()) { - return Cards.updateAsync( + return Cards.update( { _id: this.linkedId }, { $addToSet: { assignees: assigneeId } }, ); @@ -1248,7 +1220,7 @@ Cards.helpers({ const board = ReactiveCache.getBoard(this.linkedId); return board.addAssignee(assigneeId); } else { - return Cards.updateAsync( + return Cards.update( { _id: this._id }, { $addToSet: { assignees: assigneeId } }, ); @@ -1257,7 +1229,7 @@ Cards.helpers({ unassignMember(memberId) { if (this.isLinkedCard()) { - return Cards.updateAsync( + return Cards.update( { _id: this.linkedId }, { $pull: { members: memberId } }, ); @@ -1265,13 +1237,13 @@ Cards.helpers({ const board = ReactiveCache.getBoard(this.linkedId); return board.removeMember(memberId); } else { - return Cards.updateAsync({ _id: this._id }, { $pull: { members: memberId } }); + return Cards.update({ _id: this._id }, { $pull: { members: memberId } }); } }, unassignAssignee(assigneeId) { if (this.isLinkedCard()) { - return Cards.updateAsync( + return Cards.update( { _id: this.linkedId }, { $pull: { assignees: assigneeId } }, ); @@ -1279,7 +1251,7 @@ Cards.helpers({ const board = ReactiveCache.getBoard(this.linkedId); return board.removeAssignee(assigneeId); } else { - return Cards.updateAsync( + return Cards.update( { _id: this._id }, { $pull: { assignees: assigneeId } }, ); @@ -1287,8 +1259,7 @@ Cards.helpers({ }, toggleMember(memberId) { - const members = this.getMembers(); - if (members && members.indexOf(memberId) > -1) { + if (this.getMembers() && this.getMembers().indexOf(memberId) > -1) { return this.unassignMember(memberId); } else { return this.assignMember(memberId); @@ -1296,8 +1267,7 @@ Cards.helpers({ }, toggleAssignee(assigneeId) { - const assignees = this.getAssignees(); - if (assignees && assignees.indexOf(assigneeId) > -1) { + if (this.getAssignees() && this.getAssignees().indexOf(assigneeId) > -1) { return this.unassignAssignee(assigneeId); } else { return this.assignAssignee(assigneeId); @@ -2014,42 +1984,53 @@ Cards.helpers({ } return pokerWinnersListMap[0].pokerCard; }, +}); +Cards.mutations({ applyToChildren(funct) { - const cards = ReactiveCache.getCards({ parentId: this._id }); - for (const card of cards) { + ReactiveCache.getCards({ + parentId: this._id, + }).forEach(card => { funct(card); - } + }); }, archive() { this.applyToChildren(card => { - card.archive(); - }); - return Cards.updateAsync(this._id, { - $set: { archived: true, archivedAt: new Date() }, + return card.archive(); }); + return { + $set: { + archived: true, + archivedAt: new Date(), + }, + }; }, restore() { this.applyToChildren(card => { - card.restore(); - }); - return Cards.updateAsync(this._id, { - $set: { archived: false }, + return card.restore(); }); + return { + $set: { + archived: false, + }, + }; }, - moveToEndOfList({ listId, swimlaneId } = {}) { - swimlaneId = swimlaneId || this.swimlaneId; + moveToEndOfList({ listId } = {}) { + let swimlaneId = this.swimlaneId; const boardId = this.boardId; let sortIndex = 0; + // This should never happen, but there was a bug that was fixed in commit + // ea0239538a68e225c867411a4f3e0d27c158383. if (!swimlaneId) { const board = ReactiveCache.getBoard(boardId); swimlaneId = board.getDefaultSwimline()._id; } - let parentElementDom = $(`#swimlane-${swimlaneId}`).get(0); + // Move the minicard to the end of the target list + let parentElementDom = $(`#swimlane-${this.swimlaneId}`).get(0); if (!parentElementDom) parentElementDom = $(':root'); const lastCardDom = $(parentElementDom) @@ -2068,6 +2049,8 @@ Cards.helpers({ moveOptionalArgs({ boardId, swimlaneId, listId, sort } = {}) { boardId = boardId || this.boardId; swimlaneId = swimlaneId || this.swimlaneId; + // This should never happen, but there was a bug that was fixed in commit + // ea0239538a68e225c867411a4f3e0d27c158383. if (!swimlaneId) { const board = ReactiveCache.getBoard(boardId); swimlaneId = board.getDefaultSwimline()._id; @@ -2078,20 +2061,20 @@ Cards.helpers({ }, move(boardId, swimlaneId, listId, sort = null) { - const previousState = { - boardId: this.boardId, - swimlaneId: this.swimlaneId, - listId: this.listId, - sort: this.sort, + const mutatedFields = { + boardId, + swimlaneId, + listId, }; - const mutatedFields = { boardId, swimlaneId, listId }; - if (sort !== null) { mutatedFields.sort = sort; } + // we must only copy the labels and custom fields if the target board + // differs from the source board if (this.boardId !== boardId) { + // Get label names const oldBoard = ReactiveCache.getBoard(this.boardId); const oldBoardLabels = oldBoard.labels; const oldCardLabels = _.pluck( @@ -2102,10 +2085,6 @@ Cards.helpers({ ); const newBoard = ReactiveCache.getBoard(boardId); - const allowedMemberIds = _.pluck( - _.filter(newBoard.members || [], member => member.isActive === true), - 'userId', - ); const newBoardLabels = newBoard.labels; const newCardLabelIds = _.pluck( _.filter(newBoardLabels, label => { @@ -2114,6 +2093,7 @@ Cards.helpers({ '_id', ); + // assign the new card number from the target board const newCardNumber = newBoard.getNextCardNumber(); Object.assign(mutatedFields, { @@ -2122,76 +2102,29 @@ Cards.helpers({ }); mutatedFields.customFields = this.mapCustomFieldsToBoard(newBoard._id); - - // Ensure customFields is always an array (guards against legacy {} data) - if (!Array.isArray(mutatedFields.customFields)) { - mutatedFields.customFields = []; - } - - const currentMembers = Array.isArray(this.members) ? this.members : []; - const filteredMembers = currentMembers.filter(memberId => allowedMemberIds.includes(memberId)); - if (_.difference(currentMembers, filteredMembers).length > 0) { - mutatedFields.members = filteredMembers; - } - - const currentWatchers = Array.isArray(this.watchers) ? this.watchers : []; - const filteredWatchers = currentWatchers.filter(watcherId => allowedMemberIds.includes(watcherId)); - if (_.difference(currentWatchers, filteredWatchers).length > 0) { - mutatedFields.watchers = filteredWatchers; - } } - Cards.updateAsync(this._id, { $set: mutatedFields }); - - if (Meteor.isServer && Meteor.userId() && typeof UserPositionHistory !== 'undefined') { - try { - UserPositionHistory.trackChange({ - userId: Meteor.userId(), - boardId: this.boardId, - entityType: 'card', - entityId: this._id, - actionType: 'move', - previousState, - newState: { - boardId, - swimlaneId, - listId, - sort: sort !== null ? sort : this.sort, - }, - }); - } catch (e) { - console.warn('Failed to track card move in history:', e); - } - } - - if (Meteor.isServer) { - const updateMeta = {}; - if (mutatedFields.boardId !== undefined) updateMeta['meta.boardId'] = mutatedFields.boardId; - if (mutatedFields.listId !== undefined) updateMeta['meta.listId'] = mutatedFields.listId; - if (mutatedFields.swimlaneId !== undefined) updateMeta['meta.swimlaneId'] = mutatedFields.swimlaneId; - - if (Object.keys(updateMeta).length > 0) { - try { - Attachments.collection.updateAsync( - { 'meta.cardId': this._id }, - { $set: updateMeta }, - { multi: true }, - ); - } catch (err) { - console.error('Failed to update attachments metadata after moving card', this._id, err); - } - } - } + Cards.update(this._id, { + $set: mutatedFields, + }); }, addLabel(labelId) { this.labelIds.push(labelId); - return Cards.updateAsync(this._id, { $addToSet: { labelIds: labelId } }); + return { + $addToSet: { + labelIds: labelId, + }, + }; }, removeLabel(labelId) { this.labelIds = _.without(this.labelIds, labelId); - return Cards.updateAsync(this._id, { $pull: { labelIds: labelId } }); + return { + $pull: { + labelIds: labelId, + }, + }; }, toggleLabel(labelId) { @@ -2206,23 +2139,57 @@ Cards.helpers({ if (newColor === 'white') { newColor = null; } - return Cards.updateAsync(this._id, { $set: { color: newColor } }); + return { + $set: { + color: newColor, + }, + }; }, assignMember(memberId) { - return Cards.updateAsync(this._id, { $addToSet: { members: memberId } }); + return { + $addToSet: { + members: memberId, + }, + }; }, assignAssignee(assigneeId) { - return Cards.updateAsync(this._id, { $addToSet: { assignees: assigneeId } }); + // If there is not any assignee, allow one assignee, not more. + /* + if (this.getAssignees().length === 0) { + return { + $addToSet: { + assignees: assigneeId, + }, + }; + */ + // Allow more that one assignee: + // https://github.com/wekan/wekan/issues/3302 + return { + $addToSet: { + assignees: assigneeId, + }, + }; + //} else { + // return false, + //} }, unassignMember(memberId) { - return Cards.updateAsync(this._id, { $pull: { members: memberId } }); + return { + $pull: { + members: memberId, + }, + }; }, unassignAssignee(assigneeId) { - return Cards.updateAsync(this._id, { $pull: { assignees: assigneeId } }); + return { + $pull: { + assignees: assigneeId, + }, + }; }, toggleMember(memberId) { @@ -2242,15 +2209,24 @@ Cards.helpers({ }, assignCustomField(customFieldId) { - return Cards.updateAsync(this._id, { - $addToSet: { customFields: { _id: customFieldId, value: null } }, - }); + return { + $addToSet: { + customFields: { + _id: customFieldId, + value: null, + }, + }, + }; }, unassignCustomField(customFieldId) { - return Cards.updateAsync(this._id, { - $pull: { customFields: { _id: customFieldId } }, - }); + return { + $pull: { + customFields: { + _id: customFieldId, + }, + }, + }; }, toggleCustomField(customFieldId) { @@ -2262,69 +2238,150 @@ Cards.helpers({ }, toggleShowActivities() { - return Cards.updateAsync(this._id, { - $set: { showActivities: !this.showActivities }, - }); + return { + $set: { + showActivities: !this.showActivities, + } + }; }, - toggleShowChecklistAtMinicard() { - return Cards.updateAsync(this._id, { - $set: { showChecklistAtMinicard: !this.showChecklistAtMinicard }, - }); + toggleHideFinishedChecklist() { + return { + $set: { + hideFinishedChecklistIfItemsAreHidden: !this.hideFinishedChecklistIfItemsAreHidden, + } + }; }, setCustomField(customFieldId, value) { + // todo const index = this.customFieldIndex(customFieldId); if (index > -1) { - const update = { $set: {} }; + const update = { + $set: {}, + }; update.$set[`customFields.${index}.value`] = value; - return Cards.updateAsync(this._id, update); + return update; } + // TODO + // Ignatz 18.05.2018: Return null to silence ESLint. No Idea if that is correct return null; }, setCover(coverId) { - return Cards.updateAsync(this._id, { $set: { coverId } }); + return { + $set: { + coverId, + }, + }; }, unsetCover() { - return Cards.updateAsync(this._id, { $unset: { coverId: '' } }); + return { + $unset: { + coverId: '', + }, + }; }, + //setReceived(receivedAt) { + // return { + // $set: { + // receivedAt, + // }, + // }; + //}, + unsetReceived() { - return Cards.updateAsync(this._id, { $unset: { receivedAt: '' } }); + return { + $unset: { + receivedAt: '', + }, + }; }, + //setStart(startAt) { + // return { + // $set: { + // startAt, + // }, + // }; + //}, + unsetStart() { - return Cards.updateAsync(this._id, { $unset: { startAt: '' } }); + return { + $unset: { + startAt: '', + }, + }; }, + //setDue(dueAt) { + // return { + // $set: { + // dueAt, + // }, + // }; + //}, + unsetDue() { - return Cards.updateAsync(this._id, { $unset: { dueAt: '' } }); + return { + $unset: { + dueAt: '', + }, + }; }, + //setEnd(endAt) { + // return { + // $set: { + // endAt, + // }, + // }; + //}, + unsetEnd() { - return Cards.updateAsync(this._id, { $unset: { endAt: '' } }); + return { + $unset: { + endAt: '', + }, + }; }, setOvertime(isOvertime) { - return Cards.updateAsync(this._id, { $set: { isOvertime } }); + return { + $set: { + isOvertime, + }, + }; }, setSpentTime(spentTime) { - return Cards.updateAsync(this._id, { $set: { spentTime } }); + return { + $set: { + spentTime, + }, + }; }, unsetSpentTime() { - return Cards.updateAsync(this._id, { $unset: { spentTime: '', isOvertime: false } }); + return { + $unset: { + spentTime: '', + isOvertime: false, + }, + }; }, setParentId(parentId) { - return Cards.updateAsync(this._id, { $set: { parentId } }); + return { + $set: { + parentId, + }, + }; }, - setVoteQuestion(question, publicVote, allowNonBoardMembers) { - return Cards.updateAsync(this._id, { + return { $set: { vote: { question, @@ -2334,42 +2391,61 @@ Cards.helpers({ negative: [], }, }, - }); + }; }, - unsetVote() { - return Cards.updateAsync(this._id, { $unset: { vote: '' } }); + return { + $unset: { + vote: '', + }, + }; }, - setVoteEnd(end) { - return Cards.updateAsync(this._id, { $set: { 'vote.end': end } }); + return { + $set: { 'vote.end': end }, + }; }, - unsetVoteEnd() { - return Cards.updateAsync(this._id, { $unset: { 'vote.end': '' } }); + return { + $unset: { 'vote.end': '' }, + }; }, - setVote(userId, forIt) { switch (forIt) { case true: - return Cards.updateAsync(this._id, { - $pull: { 'vote.negative': userId }, - $addToSet: { 'vote.positive': userId }, - }); + // vote for it + return { + $pull: { + 'vote.negative': userId, + }, + $addToSet: { + 'vote.positive': userId, + }, + }; case false: - return Cards.updateAsync(this._id, { - $pull: { 'vote.positive': userId }, - $addToSet: { 'vote.negative': userId }, - }); + // vote against + return { + $pull: { + 'vote.positive': userId, + }, + $addToSet: { + 'vote.negative': userId, + }, + }; + default: - return Cards.updateAsync(this._id, { - $pull: { 'vote.positive': userId, 'vote.negative': userId }, - }); + // Remove votes + return { + $pull: { + 'vote.positive': userId, + 'vote.negative': userId, + }, + }; } }, setPokerQuestion(question, allowNonBoardMembers) { - return Cards.updateAsync(this._id, { + return { $set: { poker: { question, @@ -2386,47 +2462,246 @@ Cards.helpers({ unsure: [], }, }, - }); + }; }, - setPokerEstimation(estimation) { - return Cards.updateAsync(this._id, { $set: { 'poker.estimation': estimation } }); + return { + $set: { 'poker.estimation': estimation }, + }; }, - unsetPokerEstimation() { - return Cards.updateAsync(this._id, { $unset: { 'poker.estimation': '' } }); + return { + $unset: { 'poker.estimation': '' }, + }; }, - unsetPoker() { - return Cards.updateAsync(this._id, { $unset: { poker: '' } }); + return { + $unset: { + poker: '', + }, + }; }, - setPokerEnd(end) { - return Cards.updateAsync(this._id, { $set: { 'poker.end': end } }); + return { + $set: { 'poker.end': end }, + }; }, - unsetPokerEnd() { - return Cards.updateAsync(this._id, { $unset: { 'poker.end': '' } }); + return { + $unset: { 'poker.end': '' }, + }; }, - setPoker(userId, state) { - const pokerFields = ['one', 'two', 'three', 'five', 'eight', 'thirteen', 'twenty', 'forty', 'oneHundred', 'unsure']; - const pullFields = {}; - pokerFields.forEach(f => { pullFields[`poker.${f}`] = userId; }); + switch (state) { + case 'one': + // poker one + return { + $pull: { + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.one': userId, + }, + }; + case 'two': + // poker two + return { + $pull: { + 'poker.one': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.two': userId, + }, + }; - if (pokerFields.includes(state)) { - delete pullFields[`poker.${state}`]; - return Cards.updateAsync(this._id, { - $pull: pullFields, - $addToSet: { [`poker.${state}`]: userId }, - }); - } else { - return Cards.updateAsync(this._id, { $pull: pullFields }); + case 'three': + // poker three + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.three': userId, + }, + }; + + case 'five': + // poker five + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.five': userId, + }, + }; + + case 'eight': + // poker eight + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.eight': userId, + }, + }; + + case 'thirteen': + // poker thirteen + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.thirteen': userId, + }, + }; + + case 'twenty': + // poker twenty + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.twenty': userId, + }, + }; + + case 'forty': + // poker forty + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.forty': userId, + }, + }; + + case 'oneHundred': + // poker one hundred + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.unsure': userId, + }, + $addToSet: { + 'poker.oneHundred': userId, + }, + }; + + case 'unsure': + // poker unsure + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + }, + $addToSet: { + 'poker.unsure': userId, + }, + }; + + default: + // Remove pokers + return { + $pull: { + 'poker.one': userId, + 'poker.two': userId, + 'poker.three': userId, + 'poker.five': userId, + 'poker.eight': userId, + 'poker.thirteen': userId, + 'poker.twenty': userId, + 'poker.forty': userId, + 'poker.oneHundred': userId, + 'poker.unsure': userId, + }, + }; } }, - replayPoker() { - return Cards.updateAsync(this._id, { + return { $set: { 'poker.one': [], 'poker.two': [], @@ -2439,40 +2714,39 @@ Cards.helpers({ 'poker.oneHundred': [], 'poker.unsure': [], }, - }); + }; }, }); //FUNCTIONS FOR creation of Activities -async function updateActivities(doc, fieldNames, modifier) { +function updateActivities(doc, fieldNames, modifier) { if (_.contains(fieldNames, 'labelIds') && _.contains(fieldNames, 'boardId')) { - const activities = await ReactiveCache.getActivities({ + ReactiveCache.getActivities({ activityType: 'addedLabel', cardId: doc._id, - }); - for (const a of activities) { + }).forEach(a => { const lidx = doc.labelIds.indexOf(a.labelId); if (lidx !== -1 && modifier.$set.labelIds.length > lidx) { - await Activities.updateAsync(a._id, { + Activities.update(a._id, { $set: { labelId: modifier.$set.labelIds[doc.labelIds.indexOf(a.labelId)], boardId: modifier.$set.boardId, }, }); } else { - await Activities.removeAsync(a._id); + Activities.remove(a._id); } - } + }); } else if (_.contains(fieldNames, 'boardId')) { - await Activities.removeAsync({ + Activities.remove({ activityType: 'addedLabel', cardId: doc._id, }); } } -async function cardMove( +function cardMove( userId, doc, fieldNames, @@ -2481,18 +2755,15 @@ async function cardMove( oldBoardId, ) { if (_.contains(fieldNames, 'boardId') && doc.boardId !== oldBoardId) { - const newBoard = await ReactiveCache.getBoard(doc.boardId); - const oldBoard = await ReactiveCache.getBoard(oldBoardId); - const swimlane = await ReactiveCache.getSwimlane(doc.swimlaneId); - await Activities.insertAsync({ + Activities.insert({ userId, activityType: 'moveCardBoard', - boardName: newBoard.title, + boardName: ReactiveCache.getBoard(doc.boardId).title, boardId: doc.boardId, oldBoardId, - oldBoardName: oldBoard.title, + oldBoardName: ReactiveCache.getBoard(oldBoardId).title, cardId: doc._id, - swimlaneName: swimlane.title, + swimlaneName: ReactiveCache.getSwimlane(doc.swimlaneId).title, swimlaneId: doc.swimlaneId, oldSwimlaneId, }); @@ -2500,43 +2771,40 @@ async function cardMove( (_.contains(fieldNames, 'listId') && doc.listId !== oldListId) || (_.contains(fieldNames, 'swimlaneId') && doc.swimlaneId !== oldSwimlaneId) ) { - const list = await ReactiveCache.getList(doc.listId); - const swimlane = await ReactiveCache.getSwimlane(doc.swimlaneId); - await Activities.insertAsync({ + Activities.insert({ userId, oldListId, activityType: 'moveCard', - listName: list.title, + listName: ReactiveCache.getList(doc.listId).title, listId: doc.listId, boardId: doc.boardId, cardId: doc._id, cardTitle: doc.title, - swimlaneName: swimlane.title, + swimlaneName: ReactiveCache.getSwimlane(doc.swimlaneId).title, swimlaneId: doc.swimlaneId, oldSwimlaneId, }); } } -async function cardState(userId, doc, fieldNames) { +function cardState(userId, doc, fieldNames) { if (_.contains(fieldNames, 'archived')) { - const list = await ReactiveCache.getList(doc.listId); if (doc.archived) { - await Activities.insertAsync({ + Activities.insert({ userId, activityType: 'archivedCard', - listName: list.title, + listName: ReactiveCache.getList(doc.listId).title, boardId: doc.boardId, listId: doc.listId, cardId: doc._id, swimlaneId: doc.swimlaneId, }); } else { - await Activities.insertAsync({ + Activities.insert({ userId, activityType: 'restoredCard', boardId: doc.boardId, - listName: list.title, + listName: ReactiveCache.getList(doc.listId).title, listId: doc.listId, cardId: doc._id, swimlaneId: doc.swimlaneId, @@ -2545,16 +2813,15 @@ async function cardState(userId, doc, fieldNames) { } } -async function cardMembers(userId, doc, fieldNames, modifier) { +function cardMembers(userId, doc, fieldNames, modifier) { if (!_.contains(fieldNames, 'members')) return; let memberId; // Say hello to the new member if (modifier.$addToSet && modifier.$addToSet.members) { memberId = modifier.$addToSet.members; - const user = await ReactiveCache.getUser(memberId); - const username = user.username; + const username = ReactiveCache.getUser(memberId).username; if (!_.contains(doc.members, memberId)) { - await Activities.insertAsync({ + Activities.insert({ userId, username, activityType: 'joinMember', @@ -2570,11 +2837,10 @@ async function cardMembers(userId, doc, fieldNames, modifier) { // Say goodbye to the former member if (modifier.$pull && modifier.$pull.members) { memberId = modifier.$pull.members; - const user = await ReactiveCache.getUser(memberId); - const username = user.username; + const username = ReactiveCache.getUser(memberId).username; // Check that the former member is member of the card if (_.contains(doc.members, memberId)) { - await Activities.insertAsync({ + Activities.insert({ userId, username, activityType: 'unjoinMember', @@ -2588,16 +2854,15 @@ async function cardMembers(userId, doc, fieldNames, modifier) { } } -async function cardAssignees(userId, doc, fieldNames, modifier) { +function cardAssignees(userId, doc, fieldNames, modifier) { if (!_.contains(fieldNames, 'assignees')) return; let assigneeId; // Say hello to the new assignee if (modifier.$addToSet && modifier.$addToSet.assignees) { assigneeId = modifier.$addToSet.assignees; - const user = await ReactiveCache.getUser(assigneeId); - const username = user.username; + const username = ReactiveCache.getUser(assigneeId).username; if (!_.contains(doc.assignees, assigneeId)) { - await Activities.insertAsync({ + Activities.insert({ userId, username, activityType: 'joinAssignee', @@ -2612,11 +2877,10 @@ async function cardAssignees(userId, doc, fieldNames, modifier) { // Say goodbye to the former assignee if (modifier.$pull && modifier.$pull.assignees) { assigneeId = modifier.$pull.assignees; - const user = await ReactiveCache.getUser(assigneeId); - const username = user.username; + const username = ReactiveCache.getUser(assigneeId).username; // Check that the former assignee is assignee of the card if (_.contains(doc.assignees, assigneeId)) { - await Activities.insertAsync({ + Activities.insert({ userId, username, activityType: 'unjoinAssignee', @@ -2720,18 +2984,16 @@ function cardCustomFields(userId, doc, fieldNames, modifier) { } } -async function cardCreation(userId, doc) { - const list = await ReactiveCache.getList(doc.listId); - const swimlane = await ReactiveCache.getSwimlane(doc.swimlaneId); - await Activities.insertAsync({ +function cardCreation(userId, doc) { + Activities.insert({ userId, activityType: 'createCard', boardId: doc.boardId, - listName: list.title, + listName: ReactiveCache.getList(doc.listId).title, listId: doc.listId, cardId: doc._id, cardTitle: doc.title, - swimlaneName: swimlane.title, + swimlaneName: ReactiveCache.getSwimlane(doc.swimlaneId).title, swimlaneId: doc.swimlaneId, }); } @@ -2776,15 +3038,13 @@ function cardRemover(userId, doc) { }); } -const findDueCards = async days => { - const seekDue = async ($from, $to, activityType) => { - const cards = await ReactiveCache.getCards({ +const findDueCards = days => { + const seekDue = ($from, $to, activityType) => { + ReactiveCache.getCards({ archived: false, dueAt: { $gte: $from, $lt: $to }, - }); - for (const card of cards) { - const user = await ReactiveCache.getUser(card.userId); - const username = user.username; + }).forEach(card => { + const username = ReactiveCache.getUser(card.userId).username; const activity = { userId: card.userId, username, @@ -2796,15 +3056,15 @@ const findDueCards = async days => { timeValue: card.dueAt, swimlaneId: card.swimlaneId, }; - await Activities.insertAsync(activity); - } + Activities.insert(activity); + }); }; const now = new Date(), aday = 3600 * 24 * 1e3, then = day => new Date(now.setHours(0, 0, 0, 0) + day * aday); if (!days) return; if (!days.map) days = [days]; - for (const day of days) { + days.map(day => { let args = []; if (day === 0) { args = [then(0), then(1), 'duenow']; @@ -2813,8 +3073,8 @@ const findDueCards = async days => { } else { args = [then(day), now, 'pastdue']; } - await seekDue(...args); - } + seekDue(...args); + }); }; const addCronJob = _.debounce( Meteor.bindEnvironment(function findDueCardsDebounced() { @@ -2857,14 +3117,14 @@ const addCronJob = _.debounce( if (Meteor.isServer) { Meteor.methods({ // Secure poker voting: only the caller's userId is modified - async 'cards.pokerVote'(cardId, state) { + 'cards.pokerVote'(cardId, state) { check(cardId, String); if (state !== undefined && state !== null) check(state, String); if (!this.userId) throw new Meteor.Error('not-authorized'); - const card = await ReactiveCache.getCard(cardId) || await Cards.findOneAsync(cardId); + const card = ReactiveCache.getCard(cardId) || Cards.findOne(cardId); if (!card) throw new Meteor.Error('not-found'); - const board = await ReactiveCache.getBoard(card.boardId) || await Boards.findOneAsync(card.boardId); + const board = ReactiveCache.getBoard(card.boardId) || Boards.findOne(card.boardId); if (!board) throw new Meteor.Error('not-found'); const isMember = allowIsBoardMember(this.userId, board); @@ -2876,19 +3136,19 @@ if (Meteor.isServer) { let mod = card.setPoker(this.userId, state); if (!mod || typeof mod !== 'object') mod = {}; mod.$set = Object.assign({}, mod.$set, { modifiedAt: new Date(), dateLastActivity: new Date() }); - return await Cards.updateAsync({ _id: cardId }, mod); + return Cards.update({ _id: cardId }, mod); }, // Configure planning poker on a card (members only) - async 'cards.setPokerQuestion'(cardId, question, allowNonBoardMembers) { + 'cards.setPokerQuestion'(cardId, question, allowNonBoardMembers) { check(cardId, String); check(question, Boolean); check(allowNonBoardMembers, Boolean); if (!this.userId) throw new Meteor.Error('not-authorized'); - const card = await ReactiveCache.getCard(cardId) || await Cards.findOneAsync(cardId); + const card = ReactiveCache.getCard(cardId) || Cards.findOne(cardId); if (!card) throw new Meteor.Error('not-found'); - const board = await ReactiveCache.getBoard(card.boardId) || await Boards.findOneAsync(card.boardId); + const board = ReactiveCache.getBoard(card.boardId) || Boards.findOne(card.boardId); if (!allowIsBoardMember(this.userId, board)) throw new Meteor.Error('not-authorized'); const modifier = { @@ -2902,96 +3162,96 @@ if (Meteor.isServer) { dateLastActivity: new Date(), }, }; - return await Cards.updateAsync({ _id: cardId }, modifier); + return Cards.update({ _id: cardId }, modifier); }, - async 'cards.setPokerEnd'(cardId, end) { + 'cards.setPokerEnd'(cardId, end) { check(cardId, String); check(end, Date); if (!this.userId) throw new Meteor.Error('not-authorized'); - const card = await ReactiveCache.getCard(cardId) || await Cards.findOneAsync(cardId); + const card = ReactiveCache.getCard(cardId) || Cards.findOne(cardId); if (!card) throw new Meteor.Error('not-found'); - const board = await ReactiveCache.getBoard(card.boardId) || await Boards.findOneAsync(card.boardId); + const board = ReactiveCache.getBoard(card.boardId) || Boards.findOne(card.boardId); if (!allowIsBoardMember(this.userId, board)) throw new Meteor.Error('not-authorized'); const modifier = { $set: { 'poker.end': end, modifiedAt: new Date(), dateLastActivity: new Date() }, }; - return await Cards.updateAsync({ _id: cardId }, modifier); + return Cards.update({ _id: cardId }, modifier); }, - async 'cards.unsetPokerEnd'(cardId) { + 'cards.unsetPokerEnd'(cardId) { check(cardId, String); if (!this.userId) throw new Meteor.Error('not-authorized'); - const card = await ReactiveCache.getCard(cardId) || await Cards.findOneAsync(cardId); + const card = ReactiveCache.getCard(cardId) || Cards.findOne(cardId); if (!card) throw new Meteor.Error('not-found'); - const board = await ReactiveCache.getBoard(card.boardId) || await Boards.findOneAsync(card.boardId); + const board = ReactiveCache.getBoard(card.boardId) || Boards.findOne(card.boardId); if (!allowIsBoardMember(this.userId, board)) throw new Meteor.Error('not-authorized'); const modifier = { $unset: { 'poker.end': '' }, $set: { modifiedAt: new Date(), dateLastActivity: new Date() }, }; - return await Cards.updateAsync({ _id: cardId }, modifier); + return Cards.update({ _id: cardId }, modifier); }, - async 'cards.unsetPoker'(cardId) { + 'cards.unsetPoker'(cardId) { check(cardId, String); if (!this.userId) throw new Meteor.Error('not-authorized'); - const card = await ReactiveCache.getCard(cardId) || await Cards.findOneAsync(cardId); + const card = ReactiveCache.getCard(cardId) || Cards.findOne(cardId); if (!card) throw new Meteor.Error('not-found'); - const board = await ReactiveCache.getBoard(card.boardId) || await Boards.findOneAsync(card.boardId); + const board = ReactiveCache.getBoard(card.boardId) || Boards.findOne(card.boardId); if (!allowIsBoardMember(this.userId, board)) throw new Meteor.Error('not-authorized'); const modifier = { $unset: { poker: '' }, $set: { modifiedAt: new Date(), dateLastActivity: new Date() }, }; - return await Cards.updateAsync({ _id: cardId }, modifier); + return Cards.update({ _id: cardId }, modifier); }, - async 'cards.setPokerEstimation'(cardId, estimation) { + 'cards.setPokerEstimation'(cardId, estimation) { check(cardId, String); check(estimation, Number); if (!this.userId) throw new Meteor.Error('not-authorized'); - const card = await ReactiveCache.getCard(cardId) || await Cards.findOneAsync(cardId); + const card = ReactiveCache.getCard(cardId) || Cards.findOne(cardId); if (!card) throw new Meteor.Error('not-found'); - const board = await ReactiveCache.getBoard(card.boardId) || await Boards.findOneAsync(card.boardId); + const board = ReactiveCache.getBoard(card.boardId) || Boards.findOne(card.boardId); if (!allowIsBoardMember(this.userId, board)) throw new Meteor.Error('not-authorized'); const modifier = { $set: { 'poker.estimation': estimation, modifiedAt: new Date(), dateLastActivity: new Date() }, }; - return await Cards.updateAsync({ _id: cardId }, modifier); + return Cards.update({ _id: cardId }, modifier); }, - async 'cards.unsetPokerEstimation'(cardId) { + 'cards.unsetPokerEstimation'(cardId) { check(cardId, String); if (!this.userId) throw new Meteor.Error('not-authorized'); - const card = await ReactiveCache.getCard(cardId) || await Cards.findOneAsync(cardId); + const card = ReactiveCache.getCard(cardId) || Cards.findOne(cardId); if (!card) throw new Meteor.Error('not-found'); - const board = await ReactiveCache.getBoard(card.boardId) || await Boards.findOneAsync(card.boardId); + const board = ReactiveCache.getBoard(card.boardId) || Boards.findOne(card.boardId); if (!allowIsBoardMember(this.userId, board)) throw new Meteor.Error('not-authorized'); const modifier = { $unset: { 'poker.estimation': '' }, $set: { modifiedAt: new Date(), dateLastActivity: new Date() }, }; - return await Cards.updateAsync({ _id: cardId }, modifier); + return Cards.update({ _id: cardId }, modifier); }, - async 'cards.replayPoker'(cardId) { + 'cards.replayPoker'(cardId) { check(cardId, String); if (!this.userId) throw new Meteor.Error('not-authorized'); - const card = await ReactiveCache.getCard(cardId) || await Cards.findOneAsync(cardId); + const card = ReactiveCache.getCard(cardId) || Cards.findOne(cardId); if (!card) throw new Meteor.Error('not-found'); - const board = await ReactiveCache.getBoard(card.boardId) || await Boards.findOneAsync(card.boardId); + const board = ReactiveCache.getBoard(card.boardId) || Boards.findOne(card.boardId); if (!allowIsBoardMember(this.userId, board)) throw new Meteor.Error('not-authorized'); // Reset all poker votes arrays @@ -3003,19 +3263,19 @@ if (Meteor.isServer) { }, $unset: { 'poker.end': '' }, }; - return await Cards.updateAsync({ _id: cardId }, modifier); + return Cards.update({ _id: cardId }, modifier); }, // Configure voting on a card (members only) - async 'cards.setVoteQuestion'(cardId, question, publicVote, allowNonBoardMembers) { + 'cards.setVoteQuestion'(cardId, question, publicVote, allowNonBoardMembers) { check(cardId, String); check(question, String); check(publicVote, Boolean); check(allowNonBoardMembers, Boolean); if (!this.userId) throw new Meteor.Error('not-authorized'); - const card = await ReactiveCache.getCard(cardId) || await Cards.findOneAsync(cardId); + const card = ReactiveCache.getCard(cardId) || Cards.findOne(cardId); if (!card) throw new Meteor.Error('not-found'); - const board = await ReactiveCache.getBoard(card.boardId) || await Boards.findOneAsync(card.boardId); + const board = ReactiveCache.getBoard(card.boardId) || Boards.findOne(card.boardId); if (!allowIsBoardMember(this.userId, board)) throw new Meteor.Error('not-authorized'); const modifier = { @@ -3031,66 +3291,66 @@ if (Meteor.isServer) { dateLastActivity: new Date(), }, }; - return await Cards.updateAsync({ _id: cardId }, modifier); + return Cards.update({ _id: cardId }, modifier); }, - async 'cards.setVoteEnd'(cardId, end) { + 'cards.setVoteEnd'(cardId, end) { check(cardId, String); check(end, Date); if (!this.userId) throw new Meteor.Error('not-authorized'); - const card = await ReactiveCache.getCard(cardId) || await Cards.findOneAsync(cardId); + const card = ReactiveCache.getCard(cardId) || Cards.findOne(cardId); if (!card) throw new Meteor.Error('not-found'); - const board = await ReactiveCache.getBoard(card.boardId) || await Boards.findOneAsync(card.boardId); + const board = ReactiveCache.getBoard(card.boardId) || Boards.findOne(card.boardId); if (!allowIsBoardMember(this.userId, board)) throw new Meteor.Error('not-authorized'); const modifier = { $set: { 'vote.end': end, modifiedAt: new Date(), dateLastActivity: new Date() }, }; - return await Cards.updateAsync({ _id: cardId }, modifier); + return Cards.update({ _id: cardId }, modifier); }, - async 'cards.unsetVoteEnd'(cardId) { + 'cards.unsetVoteEnd'(cardId) { check(cardId, String); if (!this.userId) throw new Meteor.Error('not-authorized'); - const card = await ReactiveCache.getCard(cardId) || await Cards.findOneAsync(cardId); + const card = ReactiveCache.getCard(cardId) || Cards.findOne(cardId); if (!card) throw new Meteor.Error('not-found'); - const board = await ReactiveCache.getBoard(card.boardId) || await Boards.findOneAsync(card.boardId); + const board = ReactiveCache.getBoard(card.boardId) || Boards.findOne(card.boardId); if (!allowIsBoardMember(this.userId, board)) throw new Meteor.Error('not-authorized'); const modifier = { $unset: { 'vote.end': '' }, $set: { modifiedAt: new Date(), dateLastActivity: new Date() }, }; - return await Cards.updateAsync({ _id: cardId }, modifier); + return Cards.update({ _id: cardId }, modifier); }, - async 'cards.unsetVote'(cardId) { + 'cards.unsetVote'(cardId) { check(cardId, String); if (!this.userId) throw new Meteor.Error('not-authorized'); - const card = await ReactiveCache.getCard(cardId) || await Cards.findOneAsync(cardId); + const card = ReactiveCache.getCard(cardId) || Cards.findOne(cardId); if (!card) throw new Meteor.Error('not-found'); - const board = await ReactiveCache.getBoard(card.boardId) || await Boards.findOneAsync(card.boardId); + const board = ReactiveCache.getBoard(card.boardId) || Boards.findOne(card.boardId); if (!allowIsBoardMember(this.userId, board)) throw new Meteor.Error('not-authorized'); const modifier = { $unset: { vote: '' }, $set: { modifiedAt: new Date(), dateLastActivity: new Date() }, }; - return await Cards.updateAsync({ _id: cardId }, modifier); + return Cards.update({ _id: cardId }, modifier); }, // Secure voting: only the caller can set/unset their vote; non-members can vote only when allowed - async 'cards.vote'(cardId, forIt) { + 'cards.vote'(cardId, forIt) { check(cardId, String); // forIt may be true (upvote), false (downvote), or null/undefined (clear) if (forIt !== undefined && forIt !== null) check(forIt, Boolean); if (!this.userId) throw new Meteor.Error('not-authorized'); - const card = await ReactiveCache.getCard(cardId) || await Cards.findOneAsync(cardId); + const card = ReactiveCache.getCard(cardId) || Cards.findOne(cardId); if (!card) throw new Meteor.Error('not-found'); - const board = await ReactiveCache.getBoard(card.boardId) || await Boards.findOneAsync(card.boardId); + const board = ReactiveCache.getBoard(card.boardId) || Boards.findOne(card.boardId); if (!board) throw new Meteor.Error('not-found'); const isMember = allowIsBoardMember(this.userId, board); @@ -3121,7 +3381,7 @@ if (Meteor.isServer) { }; } - return await Cards.updateAsync({ _id: cardId }, modifier); + return Cards.update({ _id: cardId }, modifier); }, /** copies a card * <li> this method is needed on the server because attachments can only be copied on the server (access to file system) @@ -3133,7 +3393,7 @@ if (Meteor.isServer) { * @param mergeCardValues this values into the copied card * @return the new card id */ - async copyCard(cardId, boardId, swimlaneId, listId, insertAtTop, mergeCardValues) { + copyCard(cardId, boardId, swimlaneId, listId, insertAtTop, mergeCardValues) { check(cardId, String); check(boardId, String); check(swimlaneId, String); @@ -3141,10 +3401,10 @@ if (Meteor.isServer) { check(insertAtTop, Boolean); check(mergeCardValues, Object); - const card = await ReactiveCache.getCard(cardId); + const card = ReactiveCache.getCard(cardId); Object.assign(card, mergeCardValues); - const sort = await card.getSort(listId, swimlaneId, insertAtTop); + const sort = card.getSort(listId, swimlaneId, insertAtTop); if (insertAtTop) { card.sort = sort - 1; } else @@ -3152,21 +3412,21 @@ if (Meteor.isServer) { card.sort = sort + 1; } - const ret = await card.copy(boardId, swimlaneId, listId); + const ret = card.copy(boardId, swimlaneId, listId); return ret; }, }); // Cards are often fetched within a board, so we create an index to make these // queries more efficient. - Meteor.startup(async () => { - await Cards._collection.createIndexAsync({ modifiedAt: -1 }); - await Cards._collection.createIndexAsync({ boardId: 1, createdAt: -1 }); + Meteor.startup(() => { + Cards._collection.createIndex({ modifiedAt: -1 }); + Cards._collection.createIndex({ boardId: 1, createdAt: -1 }); // https://github.com/wekan/wekan/issues/1863 // Swimlane added a new field in the cards collection of mongodb named parentId. // When loading a board, mongodb is searching for every cards, the id of the parent (in the swinglanes collection). // With a huge database, this result in a very slow app and high CPU on the mongodb side. // To correct it, add Index to parentId: - await Cards._collection.createIndexAsync({ parentId: 1 }); + Cards._collection.createIndex({ parentId: 1 }); // let notifydays = parseInt(process.env.NOTIFY_DUE_DAYS_BEFORE_AND_AFTER) || 2; // default as 2 days b4 and after // let notifyitvl = parseInt(process.env.NOTIFY_DUE_AT_HOUR_OF_DAY) || 3600 * 24 * 1e3; // default interval as one day // Meteor.call("findDueCards",notifydays,notifyitvl); @@ -3175,40 +3435,40 @@ if (Meteor.isServer) { }); }); - Cards.after.insert(async (userId, doc) => { - await cardCreation(userId, doc); + Cards.after.insert((userId, doc) => { + cardCreation(userId, doc); // Track original position for new cards - Meteor.setTimeout(async () => { - const card = await Cards.findOneAsync(doc._id); + Meteor.setTimeout(() => { + const card = Cards.findOne(doc._id); if (card) { card.trackOriginalPosition(); } }, 100); }); // New activity for card (un)archivage - Cards.after.update(async (userId, doc, fieldNames) => { - await cardState(userId, doc, fieldNames); + Cards.after.update((userId, doc, fieldNames) => { + cardState(userId, doc, fieldNames); }); //New activity for card moves - Cards.after.update(async function(userId, doc, fieldNames) { + Cards.after.update(function(userId, doc, fieldNames) { const oldListId = this.previous.listId; const oldSwimlaneId = this.previous.swimlaneId; const oldBoardId = this.previous.boardId; - await cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId, oldBoardId); + cardMove(userId, doc, fieldNames, oldListId, oldSwimlaneId, oldBoardId); }); // Add a new activity if we add or remove a member to the card - Cards.before.update(async (userId, doc, fieldNames, modifier) => { - await cardMembers(userId, doc, fieldNames, modifier); - await updateActivities(doc, fieldNames, modifier); + Cards.before.update((userId, doc, fieldNames, modifier) => { + cardMembers(userId, doc, fieldNames, modifier); + updateActivities(doc, fieldNames, modifier); }); // Add a new activity if we add or remove a assignee to the card - Cards.before.update(async (userId, doc, fieldNames, modifier) => { - await cardAssignees(userId, doc, fieldNames, modifier); - await updateActivities(doc, fieldNames, modifier); + Cards.before.update((userId, doc, fieldNames, modifier) => { + cardAssignees(userId, doc, fieldNames, modifier); + updateActivities(doc, fieldNames, modifier); }); // Add a new activity if we add or remove a label to the card @@ -3222,7 +3482,7 @@ if (Meteor.isServer) { }); // Add a new activity if modify time related field like dueAt startAt etc - Cards.before.update(async (userId, doc, fieldNames, modifier) => { + Cards.before.update((userId, doc, fieldNames, modifier) => { const dla = 'dateLastActivity'; const fields = fieldNames.filter(name => name !== dla); const timingaction = ['receivedAt', 'dueAt', 'startAt', 'endAt']; @@ -3232,15 +3492,15 @@ if (Meteor.isServer) { const value = modifier.$set[action]; const oldvalue = doc[action] || ''; const activityType = `a-${action}`; - const card = await ReactiveCache.getCard(doc._id); - const list = await card.list(); + const card = ReactiveCache.getCard(doc._id); + const list = card.list(); if (list) { // change list modifiedAt, when user modified the key values in // timingaction array, if it's endAt, put the modifiedAt of list // back to one year ago for sorting purpose const modifiedAt = add(now(), -1, 'year').toISOString(); const boardId = list.boardId; - await Lists.direct.updateAsync( + Lists.direct.update( { _id: list._id, }, @@ -3252,8 +3512,7 @@ if (Meteor.isServer) { }, ); } - const user = await ReactiveCache.getUser(userId); - const username = user.username; + const username = ReactiveCache.getUser(userId).username; const activity = { userId, username, @@ -3267,7 +3526,7 @@ if (Meteor.isServer) { listId: card.listId, swimlaneId: card.swimlaneId, }; - await Activities.insertAsync(activity); + Activities.insert(activity); } }); // Remove all activities associated with a card if we remove the card @@ -3292,19 +3551,18 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/swimlanes/:swimlaneId/cards', - async function(req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramSwimlaneId = req.params.swimlaneId; Authentication.checkBoardAccess(req.userId, paramBoardId); - const cards = await ReactiveCache.getCards({ - boardId: paramBoardId, - swimlaneId: paramSwimlaneId, - archived: false, - }, - { sort: ['sort'] }); JsonRoutes.sendResult(res, { code: 200, - data: cards.map(function(doc) { + data: ReactiveCache.getCards({ + boardId: paramBoardId, + swimlaneId: paramSwimlaneId, + archived: false, + }, + { sort: ['sort'] }).map(function(doc) { return { _id: doc._id, title: doc.title, @@ -3334,22 +3592,21 @@ if (Meteor.isServer) { * title: string, * description: string}] */ - JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', async function( + JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards', function( req, res, ) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; Authentication.checkBoardAccess(req.userId, paramBoardId); - const cards = await ReactiveCache.getCards({ - boardId: paramBoardId, - listId: paramListId, - archived: false, - }, - { sort: ['sort'] }); JsonRoutes.sendResult(res, { code: 200, - data: cards.map(function(doc) { + data: ReactiveCache.getCards({ + boardId: paramBoardId, + listId: paramListId, + archived: false, + }, + { sort: ['sort'] }).map(function(doc) { return { _id: doc._id, title: doc.title, @@ -3376,9 +3633,9 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/cards/:cardId', - async function(req, res) { + function(req, res) { const paramCardId = req.params.cardId; - const card = await ReactiveCache.getCard(paramCardId); + card = ReactiveCache.getCard(paramCardId) Authentication.checkBoardAccess(req.userId, card.boardId); JsonRoutes.sendResult(res, { code: 200, @@ -3399,14 +3656,14 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/lists/:listId/cards/:cardId', - async function(req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; const paramCardId = req.params.cardId; Authentication.checkBoardAccess(req.userId, paramBoardId); JsonRoutes.sendResult(res, { code: 200, - data: await ReactiveCache.getCard({ + data: ReactiveCache.getCard({ _id: paramCardId, listId: paramListId, boardId: paramBoardId, @@ -3431,7 +3688,7 @@ if (Meteor.isServer) { * @param {string} [assignees] the assignee IDs list of the new card * @return_type {_id: string} */ - JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', async function( + JsonRoutes.add('POST', '/api/boards/:boardId/lists/:listId/cards', function( req, res, ) { @@ -3439,7 +3696,7 @@ if (Meteor.isServer) { Authentication.checkLoggedIn(req.userId); const paramBoardId = req.params.boardId; // Check user has permission to add card to the board - const board = await ReactiveCache.getBoard(paramBoardId); + const board = ReactiveCache.getBoard(paramBoardId); const addPermission = allowIsBoardMemberCommentOnly(req.userId, board); Authentication.checkAdminOrCondition(req.userId, addPermission); const paramListId = req.params.listId; @@ -3447,27 +3704,26 @@ if (Meteor.isServer) { const nextCardNumber = board.getNextCardNumber(); let customFieldsArr = []; - const customFields = await ReactiveCache.getCustomFields({'boardIds': paramBoardId}); _.forEach( - customFields, + ReactiveCache.getCustomFields({'boardIds': paramBoardId}), function (field) { if (field.automaticallyOnCard || field.alwaysOnCard) customFieldsArr.push({ _id: field._id, value: null }); }, ); - const currentCards = await ReactiveCache.getCards( + const currentCards = ReactiveCache.getCards( { listId: paramListId, archived: false, }, { sort: ['sort'] }, ); - const checkUser = await ReactiveCache.getUser(req.body.authorId); + const check = ReactiveCache.getUser(req.body.authorId); const members = req.body.members; const assignees = req.body.assignees; - if (typeof checkUser !== 'undefined') { - const id = await Cards.direct.insertAsync({ + if (typeof check !== 'undefined') { + const id = Cards.direct.insert({ title: req.body.title, boardId: paramBoardId, listId: paramListId, @@ -3488,8 +3744,8 @@ if (Meteor.isServer) { }, }); - const card = await ReactiveCache.getCard(id); - await cardCreation(req.body.authorId, card); + const card = ReactiveCache.getCard(id); + cardCreation(req.body.authorId, card); } else { JsonRoutes.sendResult(res, { code: 401, @@ -3504,21 +3760,20 @@ if (Meteor.isServer) { * @param {string} boardId the board ID * @return_type {board_cards_count: integer} */ -JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', async function( +JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', function( req, res, ) { try { const paramBoardId = req.params.boardId; Authentication.checkBoardAccess(req.userId, paramBoardId); - const cards = await ReactiveCache.getCards({ - boardId: paramBoardId, - archived: false, - }); JsonRoutes.sendResult(res, { code: 200, data: { - board_cards_count: cards.length, + board_cards_count: ReactiveCache.getCards({ + boardId: paramBoardId, + archived: false, + }).length, } }); } catch (error) { @@ -3537,7 +3792,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', async function( * @param {string} listId the List ID * @return_type {list_cards_count: integer} */ - JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards_count', async function( + JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId/cards_count', function( req, res, ) { @@ -3545,15 +3800,14 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', async function( const paramBoardId = req.params.boardId; const paramListId = req.params.listId; Authentication.checkBoardAccess(req.userId, paramBoardId); - const cards = await ReactiveCache.getCards({ - boardId: paramBoardId, - listId: paramListId, - archived: false, - }); JsonRoutes.sendResult(res, { code: 200, data: { - list_cards_count: cards.length, + list_cards_count: ReactiveCache.getCards({ + boardId: paramBoardId, + listId: paramListId, + archived: false, + }).length, } }); } catch (error) { @@ -3622,7 +3876,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', async function( JsonRoutes.add( 'PUT', '/api/boards/:boardId/lists/:listId/cards/:cardId', - async function(req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramCardId = req.params.cardId; const paramListId = req.params.listId; @@ -3630,7 +3884,7 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', async function( const newSwimlaneId = req.body.newSwimlaneId; const newListId = req.body.newListId; let updated = false; - Authentication.checkBoardWriteAccess(req.userId, paramBoardId); + Authentication.checkBoardAccess(req.userId, paramBoardId); if (req.body.title) { // Basic client-side validation - server will handle full sanitization @@ -3975,8 +4229,8 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', async function( ); updated = true; - const card = await ReactiveCache.getCard(paramCardId); - await cardMove( + const card = ReactiveCache.getCard(paramCardId); + cardMove( req.body.authorId, card, { @@ -3986,39 +4240,8 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', async function( ); } if (newBoardId && newSwimlaneId && newListId) { - // Validate destination board write access - Authentication.checkBoardWriteAccess(req.userId, newBoardId); - - // Validate that the destination list exists and belongs to the destination board - const destList = await ReactiveCache.getList({ - _id: newListId, - boardId: newBoardId, - archived: false, - }); - if (!destList) { - JsonRoutes.sendResult(res, { - code: 404, - data: { error: 'Destination list not found or does not belong to destination board' }, - }); - return; - } - - // Validate that the destination swimlane exists and belongs to the destination board - const destSwimlane = await ReactiveCache.getSwimlane({ - _id: newSwimlaneId, - boardId: newBoardId, - archived: false, - }); - if (!destSwimlane) { - JsonRoutes.sendResult(res, { - code: 404, - data: { error: 'Destination swimlane not found or does not belong to destination board' }, - }); - return; - } - // Move the card to the new board, swimlane, and list - await Cards.direct.updateAsync( + Cards.direct.update( { _id: paramCardId, listId: paramListId, @@ -4035,8 +4258,8 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', async function( ); updated = true; - const card = await ReactiveCache.getCard(paramCardId); - await cardMove( + const card = ReactiveCache.getCard(paramCardId); + cardMove( req.userId, card, ['boardId', 'swimlaneId', 'listId'], @@ -4099,13 +4322,13 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', async function( JsonRoutes.add( 'DELETE', '/api/boards/:boardId/lists/:listId/cards/:cardId', - async function(req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; const paramCardId = req.params.cardId; - Authentication.checkBoardWriteAccess(req.userId, paramBoardId); + Authentication.checkBoardAccess(req.userId, paramBoardId); - const card = await ReactiveCache.getCard(paramCardId); + const card = ReactiveCache.getCard(paramCardId); Cards.direct.remove({ _id: paramCardId, listId: paramListId, @@ -4137,14 +4360,14 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', async function( JsonRoutes.add( 'GET', '/api/boards/:boardId/cardsByCustomField/:customFieldId/:customFieldValue', - async function(req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramCustomFieldId = req.params.customFieldId; const paramCustomFieldValue = req.params.customFieldValue; Authentication.checkBoardAccess(req.userId, paramBoardId); JsonRoutes.sendResult(res, { code: 200, - data: await ReactiveCache.getCards({ + data: ReactiveCache.getCards({ boardId: paramBoardId, customFields: { $elemMatch: { @@ -4173,14 +4396,14 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', async function( JsonRoutes.add( 'POST', '/api/boards/:boardId/lists/:listId/cards/:cardId/customFields/:customFieldId', - async function(req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramCardId = req.params.cardId; const paramListId = req.params.listId; const paramCustomFieldId = req.params.customFieldId; const paramCustomFieldValue = req.body.value; - Authentication.checkBoardWriteAccess(req.userId, paramBoardId); - const card = await ReactiveCache.getCard({ + Authentication.checkBoardAccess(req.userId, paramBoardId); + const card = ReactiveCache.getCard({ _id: paramCardId, listId: paramListId, boardId: paramBoardId, @@ -4217,83 +4440,6 @@ JsonRoutes.add('GET', '/api/boards/:boardId/cards_count', async function( }); }, ); - - /** - * @operation archive_card - * @summary Archive a card - * - * @description Archive a card - * @param {string} boardId the board ID of the card - * @param {string} listId the list ID of the card - * @param {string} cardId the ID of the card - * @return_type {_id: string, archived: boolean, archivedAt: Date} - */ - JsonRoutes.add( - 'POST', - '/api/boards/:boardId/lists/:listId/cards/:cardId/archive', - async function(req, res) { - const paramBoardId = req.params.boardId; - const paramCardId = req.params.cardId; - const paramListId = req.params.listId; - Authentication.checkBoardWriteAccess(req.userId, paramBoardId); - const card = await ReactiveCache.getCard({ - _id: paramCardId, - listId: paramListId, - boardId: paramBoardId, - archived: false, - }); - if (!card) { - throw new Meteor.Error(404, 'Card not found'); - } - await card.archive(); - JsonRoutes.sendResult(res, { - code: 200, - data: { - _id: paramCardId, - archived: true, - archivedAt: new Date(), - }, - }); - }, - ); - - /** - * @operation unarchive_card - * @summary Unarchive card - * - * @description Unarchive card - * @param {string} boardId the board ID of the card - * @param {string} listId the list ID of the card - * @param {string} cardId the ID of the card - * @return_type {_id: string, archived: boolean} - */ - JsonRoutes.add( - 'POST', - '/api/boards/:boardId/lists/:listId/cards/:cardId/unarchive', - async function(req, res) { - const paramBoardId = req.params.boardId; - const paramCardId = req.params.cardId; - const paramListId = req.params.listId; - Authentication.checkBoardWriteAccess(req.userId, paramBoardId); - const card = await ReactiveCache.getCard({ - _id: paramCardId, - listId: paramListId, - boardId: paramBoardId, - archived: true, - }); - if (!card) { - throw new Meteor.Error(404, 'Card not found'); - } - await card.restore(); - JsonRoutes.sendResult(res, { - code: 200, - data: { - _id: paramCardId, - archived: false, - }, - }); - }, - ); } // Position history tracking methods @@ -4343,10 +4489,10 @@ Cards.helpers({ hasMovedFromOriginalPosition() { const history = this.getOriginalPosition(); if (!history) return false; - + const currentSwimlaneId = this.swimlaneId || null; const currentListId = this.listId || null; - + return history.originalPosition.sort !== this.sort || history.originalSwimlaneId !== currentSwimlaneId || history.originalListId !== currentListId; @@ -4358,12 +4504,12 @@ Cards.helpers({ getOriginalPositionDescription() { const history = this.getOriginalPosition(); if (!history) return 'No original position data'; - - const swimlaneInfo = history.originalSwimlaneId ? - ` in swimlane ${history.originalSwimlaneId}` : + + const swimlaneInfo = history.originalSwimlaneId ? + ` in swimlane ${history.originalSwimlaneId}` : ' in default swimlane'; - const listInfo = history.originalListId ? - ` in list ${history.originalListId}` : + const listInfo = history.originalListId ? + ` in list ${history.originalListId}` : ''; return `Original position: ${history.originalPosition.sort || 0}${swimlaneInfo}${listInfo}`; }, diff --git a/models/checklistItems.js b/models/checklistItems.js index 3b7573ee4..946adefeb 100644 --- a/models/checklistItems.js +++ b/models/checklistItems.js @@ -70,16 +70,13 @@ ChecklistItems.attachSchema( ChecklistItems.allow({ insert(userId, doc) { - // ReadOnly users cannot create checklist items - return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId)); + return allowIsBoardMemberByCard(userId, ReactiveCache.getCard(doc.cardId)); }, update(userId, doc) { - // ReadOnly users cannot edit checklist items - return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId)); + return allowIsBoardMemberByCard(userId, ReactiveCache.getCard(doc.cardId)); }, remove(userId, doc) { - // ReadOnly users cannot delete checklist items - return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId)); + return allowIsBoardMemberByCard(userId, ReactiveCache.getCard(doc.cardId)); }, fetch: ['userId', 'cardId'], }); @@ -90,31 +87,35 @@ ChecklistItems.before.insert((userId, doc) => { } }); -ChecklistItems.helpers({ - async setTitle(title) { - return await ChecklistItems.updateAsync(this._id, { $set: { title } }); +// Mutations +ChecklistItems.mutations({ + setTitle(title) { + return { $set: { title } }; }, - async check() { - return await ChecklistItems.updateAsync(this._id, { $set: { isFinished: true } }); + check() { + return { $set: { isFinished: true } }; }, - async uncheck() { - return await ChecklistItems.updateAsync(this._id, { $set: { isFinished: false } }); + uncheck() { + return { $set: { isFinished: false } }; }, - async toggleItem() { - return await ChecklistItems.updateAsync(this._id, { $set: { isFinished: !this.isFinished } }); + toggleItem() { + return { $set: { isFinished: !this.isFinished } }; }, - async move(checklistId, sortIndex) { - const checklist = await ReactiveCache.getChecklist(checklistId); - const cardId = checklist.cardId; - return await ChecklistItems.updateAsync(this._id, { - $set: { cardId, checklistId, sort: sortIndex }, - }); + move(checklistId, sortIndex) { + const cardId = ReactiveCache.getChecklist(checklistId).cardId; + const mutatedFields = { + cardId, + checklistId, + sort: sortIndex, + }; + + return { $set: mutatedFields }; }, }); // Activities helper -async function itemCreation(userId, doc) { - const card = await ReactiveCache.getCard(doc.cardId); +function itemCreation(userId, doc) { + const card = ReactiveCache.getCard(doc.cardId); const boardId = card.boardId; Activities.insert({ userId, @@ -135,8 +136,8 @@ function itemRemover(userId, doc) { }); } -async function publishCheckActivity(userId, doc) { - const card = await ReactiveCache.getCard(doc.cardId); +function publishCheckActivity(userId, doc) { + const card = ReactiveCache.getCard(doc.cardId); const boardId = card.boardId; let activityType; if (doc.isFinished) { @@ -158,15 +159,12 @@ async function publishCheckActivity(userId, doc) { Activities.insert(act); } -async function publishChekListCompleted(userId, doc) { - const card = await ReactiveCache.getCard(doc.cardId); +function publishChekListCompleted(userId, doc) { + const card = ReactiveCache.getCard(doc.cardId); const boardId = card.boardId; const checklistId = doc.checklistId; - const checkList = await ReactiveCache.getChecklist(checklistId); - const checklistItems = await ReactiveCache.getChecklistItems({ checklistId }); - const isChecklistFinished = checkList.hideAllChecklistItems || - (checklistItems.length > 0 && checklistItems.length === checklistItems.filter(i => i.isFinished).length); - if (isChecklistFinished) { + const checkList = ReactiveCache.getChecklist(checklistId); + if (checkList.isFinished()) { const act = { userId, activityType: 'completeChecklist', @@ -181,16 +179,16 @@ async function publishChekListCompleted(userId, doc) { } } -async function publishChekListUncompleted(userId, doc) { - const card = await ReactiveCache.getCard(doc.cardId); +function publishChekListUncompleted(userId, doc) { + const card = ReactiveCache.getCard(doc.cardId); const boardId = card.boardId; const checklistId = doc.checklistId; - const checkList = await ReactiveCache.getChecklist(checklistId); + const checkList = ReactiveCache.getChecklist(checklistId); // BUGS in IFTTT Rules: https://github.com/wekan/wekan/issues/1972 // Currently in checklist all are set as uncompleted/not checked, // IFTTT Rule does not move card to other list. // If following line is negated/changed to: - // if(!isChecklistFinished){ + // if(!checkList.isFinished()){ // then unchecking of any checkbox will move card to other list, // even when all checkboxes are not yet unchecked. // What is correct code for only moving when all in list is unchecked? @@ -199,10 +197,7 @@ async function publishChekListUncompleted(userId, doc) { // find . | xargs grep 'count' -sl | grep -v .meteor | grep -v node_modules | grep -v .build // Maybe something related here? // wekan/client/components/rules/triggers/checklistTriggers.js - const uncheckItems = await ReactiveCache.getChecklistItems({ checklistId }); - const isChecklistFinished = checkList.hideAllChecklistItems || - (uncheckItems.length > 0 && uncheckItems.length === uncheckItems.filter(i => i.isFinished).length); - if (isChecklistFinished) { + if (checkList.isFinished()) { const act = { userId, activityType: 'uncompleteChecklist', @@ -219,28 +214,28 @@ async function publishChekListUncompleted(userId, doc) { // Activities if (Meteor.isServer) { - Meteor.startup(async () => { - await ChecklistItems._collection.createIndexAsync({ modifiedAt: -1 }); - await ChecklistItems._collection.createIndexAsync({ checklistId: 1 }); - await ChecklistItems._collection.createIndexAsync({ cardId: 1 }); + Meteor.startup(() => { + ChecklistItems._collection.createIndex({ modifiedAt: -1 }); + ChecklistItems._collection.createIndex({ checklistId: 1 }); + ChecklistItems._collection.createIndex({ cardId: 1 }); }); - ChecklistItems.after.update(async (userId, doc, fieldNames) => { - await publishCheckActivity(userId, doc); - await publishChekListCompleted(userId, doc, fieldNames); + ChecklistItems.after.update((userId, doc, fieldNames) => { + publishCheckActivity(userId, doc); + publishChekListCompleted(userId, doc, fieldNames); }); - ChecklistItems.before.update(async (userId, doc, fieldNames) => { - await publishChekListUncompleted(userId, doc, fieldNames); + ChecklistItems.before.update((userId, doc, fieldNames) => { + publishChekListUncompleted(userId, doc, fieldNames); }); - ChecklistItems.after.insert(async (userId, doc) => { - await itemCreation(userId, doc); + ChecklistItems.after.insert((userId, doc) => { + itemCreation(userId, doc); }); - ChecklistItems.before.remove(async (userId, doc) => { + ChecklistItems.before.remove((userId, doc) => { itemRemover(userId, doc); - const card = await ReactiveCache.getCard(doc.cardId); + const card = ReactiveCache.getCard(doc.cardId); const boardId = card.boardId; Activities.insert({ userId, @@ -271,28 +266,19 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', - async function(req, res) { + function(req, res) { const paramBoardId = req.params.boardId; - const paramCardId = req.params.cardId; - const paramChecklistId = req.params.checklistId; const paramItemId = req.params.itemId; Authentication.checkBoardAccess(req.userId, paramBoardId); - const checklistItem = await ReactiveCache.getChecklistItem(paramItemId); - if (checklistItem && checklistItem.cardId === paramCardId && checklistItem.checklistId === paramChecklistId) { - const card = await ReactiveCache.getCard(checklistItem.cardId); - if (card && card.boardId === paramBoardId) { - JsonRoutes.sendResult(res, { - code: 200, - data: checklistItem, - }); - } else { - JsonRoutes.sendResult(res, { - code: 404, - }); - } + const checklistItem = ReactiveCache.getChecklistItem(paramItemId); + if (checklistItem) { + JsonRoutes.sendResult(res, { + code: 200, + data: checklistItem, + }); } else { JsonRoutes.sendResult(res, { - code: 404, + code: 500, }); } }, @@ -312,36 +298,29 @@ if (Meteor.isServer) { JsonRoutes.add( 'POST', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items', - async function(req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramChecklistId = req.params.checklistId; const paramCardId = req.params.cardId; Authentication.checkBoardAccess(req.userId, paramBoardId); - const checklist = await ReactiveCache.getChecklist({ + const checklist = ReactiveCache.getChecklist({ _id: paramChecklistId, cardId: paramCardId, }); if (checklist) { - const card = await ReactiveCache.getCard(paramCardId); - if (card && card.boardId === paramBoardId) { - const id = ChecklistItems.insert({ - cardId: paramCardId, - checklistId: paramChecklistId, - title: req.body.title, - isFinished: false, - sort: 0, - }); - JsonRoutes.sendResult(res, { - code: 200, - data: { - _id: id, - }, - }); - } else { - JsonRoutes.sendResult(res, { - code: 404, - }); - } + const id = ChecklistItems.insert({ + cardId: paramCardId, + checklistId: paramChecklistId, + title: req.body.title, + isFinished: false, + sort: 0, + }); + JsonRoutes.sendResult(res, { + code: 200, + data: { + _id: id, + }, + }); } else { JsonRoutes.sendResult(res, { code: 404, @@ -366,28 +345,11 @@ if (Meteor.isServer) { JsonRoutes.add( 'PUT', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', - async function(req, res) { + function(req, res) { const paramBoardId = req.params.boardId; - const paramCardId = req.params.cardId; - const paramChecklistId = req.params.checklistId; const paramItemId = req.params.itemId; Authentication.checkBoardAccess(req.userId, paramBoardId); - const checklistItem = await ReactiveCache.getChecklistItem(paramItemId); - if (!checklistItem || checklistItem.cardId !== paramCardId || checklistItem.checklistId !== paramChecklistId) { - JsonRoutes.sendResult(res, { - code: 404, - }); - return; - } - const card = await ReactiveCache.getCard(checklistItem.cardId); - if (!card || card.boardId !== paramBoardId) { - JsonRoutes.sendResult(res, { - code: 404, - }); - return; - } - function isTrue(data) { try { return data.toLowerCase() === 'true'; @@ -434,28 +396,10 @@ if (Meteor.isServer) { JsonRoutes.add( 'DELETE', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId/items/:itemId', - async function(req, res) { + function(req, res) { const paramBoardId = req.params.boardId; - const paramCardId = req.params.cardId; - const paramChecklistId = req.params.checklistId; const paramItemId = req.params.itemId; Authentication.checkBoardAccess(req.userId, paramBoardId); - - const checklistItem = await ReactiveCache.getChecklistItem(paramItemId); - if (!checklistItem || checklistItem.cardId !== paramCardId || checklistItem.checklistId !== paramChecklistId) { - JsonRoutes.sendResult(res, { - code: 404, - }); - return; - } - const card = await ReactiveCache.getCard(checklistItem.cardId); - if (!card || card.boardId !== paramBoardId) { - JsonRoutes.sendResult(res, { - code: 404, - }); - return; - } - ChecklistItems.direct.remove({ _id: paramItemId }); JsonRoutes.sendResult(res, { code: 200, diff --git a/models/checklists.js b/models/checklists.js index 77196984c..d30dcc1be 100644 --- a/models/checklists.js +++ b/models/checklists.js @@ -77,29 +77,23 @@ Checklists.attachSchema( type: Boolean, optional: true, }, - showChecklistAtMinicard: { - /** - * show this checklist on minicard? - */ - type: Boolean, - defaultValue: false, - }, }), ); Checklists.helpers({ - async copy(newCardId) { + copy(newCardId) { let copyObj = Object.assign({}, this); delete copyObj._id; copyObj.cardId = newCardId; const newChecklistId = Checklists.insert(copyObj); - const items = await ReactiveCache.getChecklistItems({ checklistId: this._id }); - for (const item of items) { + ReactiveCache.getChecklistItems({ checklistId: this._id }).forEach(function( + item, + ) { item._id = null; item.checklistId = newChecklistId; item.cardId = newCardId; ChecklistItems.insert(item); - } + }); }, itemCount() { @@ -149,63 +143,33 @@ Checklists.helpers({ } return ret; }, - async checkAllItems() { - const checkItems = await ReactiveCache.getChecklistItems({ checklistId: this._id }); - for (const item of checkItems) { - await item.check(); - } + checkAllItems() { + const checkItems = ReactiveCache.getChecklistItems({ checklistId: this._id }); + checkItems.forEach(function(item) { + item.check(); + }); }, - async uncheckAllItems() { - const checkItems = await ReactiveCache.getChecklistItems({ checklistId: this._id }); - for (const item of checkItems) { - await item.uncheck(); - } + uncheckAllItems() { + const checkItems = ReactiveCache.getChecklistItems({ checklistId: this._id }); + checkItems.forEach(function(item) { + item.uncheck(); + }); }, itemIndex(itemId) { const items = ReactiveCache.getChecklist({ _id: this._id }).items; return _.pluck(items, '_id').indexOf(itemId); }, - - async setTitle(title) { - return await Checklists.updateAsync(this._id, { $set: { title } }); - }, - /** move the checklist to another card - * @param newCardId move the checklist to this cardId - */ - async move(newCardId) { - // Note: Activities and ChecklistItems updates are now handled server-side - // in the moveChecklist Meteor method to avoid client-side permission issues - return await Checklists.updateAsync(this._id, { $set: { cardId: newCardId } }); - }, - async toggleHideCheckedChecklistItems() { - return await Checklists.updateAsync(this._id, { - $set: { hideCheckedChecklistItems: !this.hideCheckedChecklistItems }, - }); - }, - async toggleHideAllChecklistItems() { - return await Checklists.updateAsync(this._id, { - $set: { hideAllChecklistItems: !this.hideAllChecklistItems }, - }); - }, - async toggleShowChecklistAtMinicard() { - return await Checklists.updateAsync(this._id, { - $set: { showChecklistAtMinicard: !this.showChecklistAtMinicard }, - }); - }, }); Checklists.allow({ insert(userId, doc) { - // ReadOnly users cannot create checklists - return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId)); + return allowIsBoardMemberByCard(userId, ReactiveCache.getCard(doc.cardId)); }, update(userId, doc) { - // ReadOnly users cannot edit checklists - return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId)); + return allowIsBoardMemberByCard(userId, ReactiveCache.getCard(doc.cardId)); }, remove(userId, doc) { - // ReadOnly users cannot delete checklists - return allowIsBoardMemberWithWriteAccessByCard(userId, Cards.findOne(doc.cardId)); + return allowIsBoardMemberByCard(userId, ReactiveCache.getCard(doc.cardId)); }, fetch: ['userId', 'cardId'], }); @@ -217,70 +181,65 @@ Checklists.before.insert((userId, doc) => { } }); - -if (Meteor.isServer) { - Meteor.methods({ - async moveChecklist(checklistId, newCardId) { - check(checklistId, String); - check(newCardId, String); - - const checklist = await ReactiveCache.getChecklist(checklistId); - if (!checklist) { - throw new Meteor.Error('checklist-not-found', 'Checklist not found'); - } - - const newCard = await ReactiveCache.getCard(newCardId); - if (!newCard) { - throw new Meteor.Error('card-not-found', 'Target card not found'); - } - - // Check permissions on both source and target cards - const sourceCard = await ReactiveCache.getCard(checklist.cardId); - if (!allowIsBoardMemberByCard(this.userId, sourceCard)) { - throw new Meteor.Error('not-authorized', 'Not authorized to move checklist from source card'); - } - if (!allowIsBoardMemberByCard(this.userId, newCard)) { - throw new Meteor.Error('not-authorized', 'Not authorized to move checklist to target card'); - } - - // Update activities - const activities = await ReactiveCache.getActivities({ checklistId }); - for (const activity of activities) { - Activities.update(activity._id, { - $set: { - cardId: newCardId, - }, - }); - } - - // Update checklist items - const checklistItems = await ReactiveCache.getChecklistItems({ checklistId }); - for (const checklistItem of checklistItems) { - ChecklistItems.update(checklistItem._id, { - $set: { - cardId: newCardId, - }, - }); - } - - // Update the checklist itself - Checklists.update(checklistId, { +Checklists.mutations({ + setTitle(title) { + return { $set: { title } }; + }, + /** move the checklist to another card + * @param newCardId move the checklist to this cardId + */ + move(newCardId) { + // update every activity + ReactiveCache.getActivities( + {checklistId: this._id} + ).forEach(activity => { + Activities.update(activity._id, { $set: { cardId: newCardId, }, }); + }); + // update every checklist-item + ReactiveCache.getChecklistItems( + {checklistId: this._id} + ).forEach(checklistItem => { + ChecklistItems.update(checklistItem._id, { + $set: { + cardId: newCardId, + }, + }); + }); + // update the checklist itself + return { + $set: { + cardId: newCardId, + }, + }; + }, + toggleHideCheckedChecklistItems() { + return { + $set: { + hideCheckedChecklistItems: !this.hideCheckedChecklistItems, + } + }; + }, + toggleHideAllChecklistItems() { + return { + $set: { + hideAllChecklistItems: !this.hideAllChecklistItems, + } + }; + }, +}); - return checklistId; - }, +if (Meteor.isServer) { + Meteor.startup(() => { + Checklists._collection.createIndex({ modifiedAt: -1 }); + Checklists._collection.createIndex({ cardId: 1, createdAt: 1 }); }); - Meteor.startup(async () => { - await Checklists._collection.createIndexAsync({ modifiedAt: -1 }); - await Checklists._collection.createIndexAsync({ cardId: 1, createdAt: 1 }); - }); - - Checklists.after.insert(async (userId, doc) => { - const card = await ReactiveCache.getCard(doc.cardId); + Checklists.after.insert((userId, doc) => { + const card = ReactiveCache.getCard(doc.cardId); Activities.insert({ userId, activityType: 'addChecklist', @@ -293,19 +252,19 @@ if (Meteor.isServer) { }); }); - Checklists.before.remove(async (userId, doc) => { - const activities = await ReactiveCache.getActivities({ checklistId: doc._id }); - const card = await ReactiveCache.getCard(doc.cardId); + Checklists.before.remove((userId, doc) => { + const activities = ReactiveCache.getActivities({ checklistId: doc._id }); + const card = ReactiveCache.getCard(doc.cardId); if (activities) { - for (const activity of activities) { + activities.forEach(activity => { Activities.remove(activity._id); - } + }); } Activities.insert({ userId, activityType: 'removeChecklist', cardId: doc.cardId, - boardId: (await ReactiveCache.getCard(doc.cardId)).boardId, + boardId: ReactiveCache.getCard(doc.cardId).boardId, checklistId: doc._id, checklistName: doc.title, listId: card.listId, @@ -327,25 +286,11 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/cards/:cardId/checklists', - async function(req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramCardId = req.params.cardId; Authentication.checkBoardAccess(req.userId, paramBoardId); - - // Verify the card belongs to the board - const card = await ReactiveCache.getCard({ - _id: paramCardId, - boardId: paramBoardId, - }); - if (!card) { - JsonRoutes.sendResult(res, { - code: 404, - data: { error: 'Card not found or does not belong to the specified board' }, - }); - return; - } - - const checklists = (await ReactiveCache.getChecklists({ cardId: paramCardId })).map(function( + const checklists = ReactiveCache.getChecklists({ cardId: paramCardId }).map(function( doc, ) { return { @@ -385,33 +330,19 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', - async function(req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramChecklistId = req.params.checklistId; const paramCardId = req.params.cardId; Authentication.checkBoardAccess(req.userId, paramBoardId); - - // Verify the card belongs to the board - const card = await ReactiveCache.getCard({ - _id: paramCardId, - boardId: paramBoardId, - }); - if (!card) { - JsonRoutes.sendResult(res, { - code: 404, - data: { error: 'Card not found or does not belong to the specified board' }, - }); - return; - } - - const checklist = await ReactiveCache.getChecklist({ + const checklist = ReactiveCache.getChecklist({ _id: paramChecklistId, cardId: paramCardId, }); if (checklist) { - checklist.items = (await ReactiveCache.getChecklistItems({ + checklist.items = ReactiveCache.getChecklistItems({ checklistId: checklist._id, - })).map(function(doc) { + }).map(function(doc) { return { _id: doc._id, title: doc.title, @@ -443,30 +374,16 @@ if (Meteor.isServer) { JsonRoutes.add( 'POST', '/api/boards/:boardId/cards/:cardId/checklists', - async function(req, res) { + function(req, res) { // Check user is logged in //Authentication.checkLoggedIn(req.userId); const paramBoardId = req.params.boardId; Authentication.checkBoardAccess(req.userId, paramBoardId); // Check user has permission to add checklist to the card - const board = await ReactiveCache.getBoard(paramBoardId); + const board = ReactiveCache.getBoard(paramBoardId); const addPermission = allowIsBoardMemberCommentOnly(req.userId, board); Authentication.checkAdminOrCondition(req.userId, addPermission); const paramCardId = req.params.cardId; - - // Verify the card belongs to the board - const card = await ReactiveCache.getCard({ - _id: paramCardId, - boardId: paramBoardId, - }); - if (!card) { - JsonRoutes.sendResult(res, { - code: 404, - data: { error: 'Card not found or does not belong to the specified board' }, - }); - return; - } - const id = Checklists.insert({ title: req.body.title, cardId: paramCardId, @@ -517,38 +434,10 @@ if (Meteor.isServer) { JsonRoutes.add( 'DELETE', '/api/boards/:boardId/cards/:cardId/checklists/:checklistId', - async function(req, res) { + function(req, res) { const paramBoardId = req.params.boardId; - const paramCardId = req.params.cardId; const paramChecklistId = req.params.checklistId; Authentication.checkBoardAccess(req.userId, paramBoardId); - - // Verify the card belongs to the board - const card = await ReactiveCache.getCard({ - _id: paramCardId, - boardId: paramBoardId, - }); - if (!card) { - JsonRoutes.sendResult(res, { - code: 404, - data: { error: 'Card not found or does not belong to the specified board' }, - }); - return; - } - - // Verify the checklist exists and belongs to the card - const checklist = await ReactiveCache.getChecklist({ - _id: paramChecklistId, - cardId: paramCardId, - }); - if (!checklist) { - JsonRoutes.sendResult(res, { - code: 404, - data: { error: 'Checklist not found or does not belong to the specified card' }, - }); - return; - } - Checklists.remove({ _id: paramChecklistId }); JsonRoutes.sendResult(res, { code: 200, diff --git a/models/counters.js b/models/counters.js index 144d5b5b9..232353fbe 100644 --- a/models/counters.js +++ b/models/counters.js @@ -1,25 +1 @@ -import { Meteor } from 'meteor/meteor'; - Counters = new Mongo.Collection('counters'); - -// Async version (for future Meteor 3.0 migration) -async function incrementCounterAsync(counterName, amount = 1) { - const result = await Counters.rawCollection().findOneAndUpdate( - { _id: counterName }, - { $inc: { next_val: amount } }, - { upsert: true, returnDocument: 'after' }, - ); - return result.value ? result.value.next_val : result.next_val; -} - -// Sync version (for Meteor 2.x autoValue compatibility) -const incrementCounter = Meteor.wrapAsync(async (counterName, amount, callback) => { - try { - const result = await incrementCounterAsync(counterName, amount); - callback(null, result); - } catch (err) { - callback(err); - } -}); - -export { incrementCounter, incrementCounterAsync }; diff --git a/models/csvCreator.js b/models/csvCreator.js index 85428ab9e..e7d83331b 100644 --- a/models/csvCreator.js +++ b/models/csvCreator.js @@ -247,7 +247,7 @@ export class CsvCreator { this.swimlane = swimlaneId; } - async createLists(csvData, boardId) { + createLists(csvData, boardId) { let numOfCreatedLists = 0; for (let i = 1; i < csvData.length; i++) { const listToCreate = { @@ -256,7 +256,7 @@ export class CsvCreator { createdAt: this._now(), }; if (csvData[i][this.fieldIndex.stage]) { - const existingList = await ReactiveCache.getLists({ + const existingList = ReactiveCache.getLists({ title: csvData[i][this.fieldIndex.stage], boardId, }); @@ -279,7 +279,7 @@ export class CsvCreator { } } - async createCards(csvData, boardId) { + createCards(csvData, boardId) { for (let i = 1; i < csvData.length; i++) { const cardToCreate = { archived: false, @@ -321,7 +321,7 @@ export class CsvCreator { } // add the labels if (csvData[i][this.fieldIndex.labels]) { - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); for (const importedLabel of csvData[i][this.fieldIndex.labels].split( ' ', )) { @@ -382,21 +382,21 @@ export class CsvCreator { } } - async create(board, currentBoardId) { + create(board, currentBoardId) { const isSandstorm = Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm; if (isSandstorm && currentBoardId) { - const currentBoard = await ReactiveCache.getBoard(currentBoardId); - await currentBoard.archive(); + const currentBoard = ReactiveCache.getBoard(currentBoardId); + currentBoard.archive(); } this.mapHeadertoCardFieldIndex(board[0]); const boardId = this.createBoard(board); - await this.createLists(board, boardId); + this.createLists(board, boardId); this.createSwimlanes(boardId); this.createCustomFields(boardId); - await this.createCards(board, boardId); + this.createCards(board, boardId); return boardId; } } diff --git a/models/customFields.js b/models/customFields.js index b227a64db..55d4cef98 100644 --- a/models/customFields.js +++ b/models/customFields.js @@ -152,14 +152,17 @@ CustomFields.addToAllCards = cf => { ); }; -CustomFields.helpers({ - async addBoard(boardId) { +CustomFields.mutations({ + addBoard(boardId) { if (boardId) { - return await CustomFields.updateAsync(this._id, { - $push: { boardIds: boardId }, - }); + return { + $push: { + boardIds: boardId, + }, + }; + } else { + return null; } - return null; }, }); @@ -167,25 +170,25 @@ CustomFields.allow({ insert(userId, doc) { return allowIsAnyBoardMember( userId, - Boards.find({ + ReactiveCache.getBoards({ _id: { $in: doc.boardIds }, - }).fetch(), + }), ); }, update(userId, doc) { return allowIsAnyBoardMember( userId, - Boards.find({ + ReactiveCache.getBoards({ _id: { $in: doc.boardIds }, - }).fetch(), + }), ); }, remove(userId, doc) { return allowIsAnyBoardMember( userId, - Boards.find({ + ReactiveCache.getBoards({ _id: { $in: doc.boardIds }, - }).fetch(), + }), ); }, fetch: ['userId', 'boardIds'], @@ -214,9 +217,9 @@ function customFieldDeletion(userId, doc) { // This has some bug, it does not show edited customField value at Outgoing Webhook, // instead it shows undefined, and no listId and swimlaneId. -async function customFieldEdit(userId, doc) { - const card = await ReactiveCache.getCard(doc.cardId); - const customFieldValue = (await ReactiveCache.getActivity({ customFieldId: doc._id })).value; +function customFieldEdit(userId, doc) { + const card = ReactiveCache.getCard(doc.cardId); + const customFieldValue = ReactiveCache.getActivity({ customFieldId: doc._id }).value; Activities.insert({ userId, activityType: 'setCustomField', @@ -229,9 +232,9 @@ async function customFieldEdit(userId, doc) { } if (Meteor.isServer) { - Meteor.startup(async () => { - await CustomFields._collection.createIndexAsync({ modifiedAt: -1 }); - await CustomFields._collection.createIndexAsync({ boardIds: 1 }); + Meteor.startup(() => { + CustomFields._collection.createIndex({ modifiedAt: -1 }); + CustomFields._collection.createIndex({ boardIds: 1 }); }); CustomFields.after.insert((userId, doc) => { @@ -242,14 +245,14 @@ if (Meteor.isServer) { } }); - CustomFields.before.update(async (userId, doc, fieldNames, modifier) => { + CustomFields.before.update((userId, doc, fieldNames, modifier) => { if (_.contains(fieldNames, 'boardIds') && modifier.$pull) { Cards.update( { boardId: modifier.$pull.boardIds, 'customFields._id': doc._id }, { $pull: { customFields: { _id: doc._id } } }, { multi: true }, ); - await customFieldEdit(userId, doc); + customFieldEdit(userId, doc); Activities.remove({ customFieldId: doc._id, boardId: modifier.$pull.boardIds, @@ -296,7 +299,7 @@ if (Meteor.isServer) { * name: string, * type: string}] */ - JsonRoutes.add('GET', '/api/boards/:boardId/custom-fields', async function( + JsonRoutes.add('GET', '/api/boards/:boardId/custom-fields', function( req, res, ) { @@ -304,7 +307,7 @@ if (Meteor.isServer) { Authentication.checkBoardAccess(req.userId, paramBoardId); JsonRoutes.sendResult(res, { code: 200, - data: (await ReactiveCache.getCustomFields({ boardIds: { $in: [paramBoardId] } })).map( + data: ReactiveCache.getCustomFields({ boardIds: { $in: [paramBoardId] } }).map( function(cf) { return { _id: cf._id, @@ -328,13 +331,13 @@ if (Meteor.isServer) { JsonRoutes.add( 'GET', '/api/boards/:boardId/custom-fields/:customFieldId', - async function(req, res) { + function(req, res) { const paramBoardId = req.params.boardId; const paramCustomFieldId = req.params.customFieldId; Authentication.checkBoardAccess(req.userId, paramBoardId); JsonRoutes.sendResult(res, { code: 200, - data: await ReactiveCache.getCustomField({ + data: ReactiveCache.getCustomField({ _id: paramCustomFieldId, boardIds: { $in: [paramBoardId] }, }), @@ -356,13 +359,13 @@ if (Meteor.isServer) { * @param {boolean} showSumAtTopOfList should the sum of the custom fields be shown at top of list? * @return_type {_id: string} */ - JsonRoutes.add('POST', '/api/boards/:boardId/custom-fields', async function( + JsonRoutes.add('POST', '/api/boards/:boardId/custom-fields', function( req, res, ) { const paramBoardId = req.params.boardId; Authentication.checkBoardAccess(req.userId, paramBoardId); - const board = await ReactiveCache.getBoard(paramBoardId); + const board = ReactiveCache.getBoard(paramBoardId); const id = CustomFields.direct.insert({ name: req.body.name, type: req.body.type, @@ -374,7 +377,7 @@ if (Meteor.isServer) { boardIds: [board._id], }); - const customField = await ReactiveCache.getCustomField({ + const customField = ReactiveCache.getCustomField({ _id: id, boardIds: { $in: [paramBoardId] }, }); @@ -409,57 +412,52 @@ if (Meteor.isServer) { const paramFieldId = req.params.customFieldId; Authentication.checkBoardAccess(req.userId, paramBoardId); - const boardScopedField = { - _id: paramFieldId, - boardIds: { $in: [paramBoardId] }, - }; - if (req.body.hasOwnProperty('name')) { CustomFields.direct.update( - boardScopedField, + { _id: paramFieldId }, { $set: { name: req.body.name } }, ); } if (req.body.hasOwnProperty('type')) { CustomFields.direct.update( - boardScopedField, + { _id: paramFieldId }, { $set: { type: req.body.type } }, ); } if (req.body.hasOwnProperty('settings')) { CustomFields.direct.update( - boardScopedField, + { _id: paramFieldId }, { $set: { settings: req.body.settings } }, ); } if (req.body.hasOwnProperty('showOnCard')) { CustomFields.direct.update( - boardScopedField, + { _id: paramFieldId }, { $set: { showOnCard: req.body.showOnCard } }, ); } if (req.body.hasOwnProperty('automaticallyOnCard')) { CustomFields.direct.update( - boardScopedField, + { _id: paramFieldId }, { $set: { automaticallyOnCard: req.body.automaticallyOnCard } }, ); } if (req.body.hasOwnProperty('alwaysOnCard')) { CustomFields.direct.update( - boardScopedField, + { _id: paramFieldId }, { $set: { alwaysOnCard: req.body.alwaysOnCard } }, ); } if (req.body.hasOwnProperty('showLabelOnMiniCard')) { CustomFields.direct.update( - boardScopedField, + { _id: paramFieldId }, { $set: { showLabelOnMiniCard: req.body.showLabelOnMiniCard } }, ); } if (req.body.hasOwnProperty('showSumAtTopOfList')) { CustomFields.direct.update( - boardScopedField, + { _id: paramFieldId }, { $set: { showSumAtTopOfList: req.body.showSumAtTopOfList } }, ); } @@ -491,10 +489,7 @@ if (Meteor.isServer) { if (req.body.hasOwnProperty('items')) { if (Array.isArray(paramItems)) { CustomFields.direct.update( - { - _id: paramCustomFieldId, - boardIds: { $in: [paramBoardId] }, - }, + { _id: paramCustomFieldId }, { $push: { 'settings.dropdownItems': { @@ -539,7 +534,6 @@ if (Meteor.isServer) { CustomFields.direct.update( { _id: paramCustomFieldId, - boardIds: { $in: [paramBoardId] }, 'settings.dropdownItems._id': paramDropdownItemId, }, { @@ -572,12 +566,12 @@ if (Meteor.isServer) { '/api/boards/:boardId/custom-fields/:customFieldId/dropdown-items/:dropdownItemId', (req, res) => { const paramBoardId = req.params.boardId; - const paramCustomFieldId = req.params.customFieldId; - const paramDropdownItemId = req.params.dropdownItemId; + paramCustomFieldId = req.params.customFieldId; + paramDropdownItemId = req.params.dropdownItemId; Authentication.checkBoardAccess(req.userId, paramBoardId); CustomFields.direct.update( - { _id: paramCustomFieldId, boardIds: { $in: [paramBoardId] } }, + { _id: paramCustomFieldId }, { $pull: { 'settings.dropdownItems': { _id: paramDropdownItemId }, diff --git a/models/export.js b/models/export.js index c72d2ff42..a45f8dc77 100644 --- a/models/export.js +++ b/models/export.js @@ -27,14 +27,14 @@ if (Meteor.isServer) { * @param {string} boardId the ID of the board we are exporting * @param {string} authToken the loginToken */ - JsonRoutes.add('get', '/api/boards/:boardId/export', async function (req, res) { + JsonRoutes.add('get', '/api/boards/:boardId/export', function (req, res) { const boardId = req.params.boardId; let user = null; let impersonateDone = false; let adminId = null; // First check if board exists and is public to avoid unnecessary authentication - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (!board) { JsonRoutes.sendResult(res, 404); return; @@ -46,7 +46,7 @@ if (Meteor.isServer) { const exporter = new Exporter(boardId); JsonRoutes.sendResult(res, { code: 200, - data: await exporter.build(), + data: exporter.build(), }); return; } @@ -64,18 +64,18 @@ if (Meteor.isServer) { } const hashToken = Accounts._hashLoginToken(loginToken); - user = await ReactiveCache.getUser({ + user = ReactiveCache.getUser({ 'services.resume.loginTokens.hashedToken': hashToken, }); adminId = user._id.toString(); - impersonateDone = await ReactiveCache.getImpersonatedUser({ adminId: adminId }); + impersonateDone = ReactiveCache.getImpersonatedUser({ adminId: adminId }); } else if (!Meteor.settings.public.sandstorm) { Authentication.checkUserId(req.userId); - user = await ReactiveCache.getUser({ _id: req.userId, isAdmin: true }); + user = ReactiveCache.getUser({ _id: req.userId, isAdmin: true }); } const exporter = new Exporter(boardId); - if (await exporter.canExport(user) || impersonateDone) { + if (exporter.canExport(user) || impersonateDone) { if (impersonateDone) { ImpersonatedUsers.insert({ adminId: adminId, @@ -86,7 +86,7 @@ if (Meteor.isServer) { JsonRoutes.sendResult(res, { code: 200, - data: await exporter.build(), + data: exporter.build(), }); } else { // we could send an explicit error message, but on the other hand the only @@ -118,7 +118,7 @@ if (Meteor.isServer) { JsonRoutes.add( 'get', '/api/boards/:boardId/attachments/:attachmentId/export', - async function (req, res) { + function (req, res) { const boardId = req.params.boardId; const attachmentId = req.params.attachmentId; let user = null; @@ -126,7 +126,7 @@ if (Meteor.isServer) { let adminId = null; // First check if board exists and is public to avoid unnecessary authentication - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (!board) { JsonRoutes.sendResult(res, 404); return; @@ -138,7 +138,7 @@ if (Meteor.isServer) { const exporter = new Exporter(boardId, attachmentId); JsonRoutes.sendResult(res, { code: 200, - data: await exporter.build(), + data: exporter.build(), }); return; } @@ -156,18 +156,18 @@ if (Meteor.isServer) { } const hashToken = Accounts._hashLoginToken(loginToken); - user = await ReactiveCache.getUser({ + user = ReactiveCache.getUser({ 'services.resume.loginTokens.hashedToken': hashToken, }); adminId = user._id.toString(); - impersonateDone = await ReactiveCache.getImpersonatedUser({ adminId: adminId }); + impersonateDone = ReactiveCache.getImpersonatedUser({ adminId: adminId }); } else if (!Meteor.settings.public.sandstorm) { Authentication.checkUserId(req.userId); - user = await ReactiveCache.getUser({ _id: req.userId, isAdmin: true }); + user = ReactiveCache.getUser({ _id: req.userId, isAdmin: true }); } const exporter = new Exporter(boardId, attachmentId); - if (await exporter.canExport(user) || impersonateDone) { + if (exporter.canExport(user) || impersonateDone) { if (impersonateDone) { ImpersonatedUsers.insert({ adminId: adminId, @@ -178,7 +178,7 @@ if (Meteor.isServer) { } JsonRoutes.sendResult(res, { code: 200, - data: await exporter.build(), + data: exporter.build(), }); } else { // we could send an explicit error message, but on the other hand the only @@ -203,14 +203,14 @@ if (Meteor.isServer) { * @param {string} authToken the loginToken * @param {string} delimiter delimiter to use while building export. Default is comma ',' */ - Picker.route('/api/boards/:boardId/export/csv', async function (params, req, res) { + Picker.route('/api/boards/:boardId/export/csv', function (params, req, res) { const boardId = params.boardId; let user = null; let impersonateDone = false; let adminId = null; // First check if board exists and is public to avoid unnecessary authentication - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (!board) { res.writeHead(404); res.end('Board not found'); @@ -237,7 +237,7 @@ if (Meteor.isServer) { // use Uint8Array to prevent from converting bytes to string res.write(new Uint8Array([0xEF, 0xBB, 0xBF])); } - res.write(await exporter.buildCsv(params.query.delimiter, 'en')); + res.write(exporter.buildCsv(params.query.delimiter, 'en')); res.end(); return; } @@ -256,21 +256,21 @@ if (Meteor.isServer) { } const hashToken = Accounts._hashLoginToken(loginToken); - user = await ReactiveCache.getUser({ + user = ReactiveCache.getUser({ 'services.resume.loginTokens.hashedToken': hashToken, }); adminId = user._id.toString(); - impersonateDone = await ReactiveCache.getImpersonatedUser({ adminId: adminId }); + impersonateDone = ReactiveCache.getImpersonatedUser({ adminId: adminId }); } else if (!Meteor.settings.public.sandstorm) { Authentication.checkUserId(req.userId); - user = await ReactiveCache.getUser({ + user = ReactiveCache.getUser({ _id: req.userId, isAdmin: true, }); } const exporter = new Exporter(boardId); - if (await exporter.canExport(user) || impersonateDone) { + if (exporter.canExport(user) || impersonateDone) { if (impersonateDone) { let exportType = 'exportCSV'; if( params.query.delimiter == "\t" ) { @@ -303,7 +303,7 @@ if (Meteor.isServer) { // use Uint8Array to prevent from converting bytes to string res.write(new Uint8Array([0xEF, 0xBB, 0xBF])); } - res.write(await exporter.buildCsv(params.query.delimiter, userLanguage)); + res.write(exporter.buildCsv(params.query.delimiter, userLanguage)); res.end(); } else { res.writeHead(403); diff --git a/models/exportExcel.js b/models/exportExcel.js index aac56cd40..598fb92d6 100644 --- a/models/exportExcel.js +++ b/models/exportExcel.js @@ -30,14 +30,14 @@ runOnServer(function() { * @param {string} boardId the ID of the board we are exporting * @param {string} authToken the loginToken */ - Picker.route('/api/boards/:boardId/exportExcel', async function (params, req, res) { + Picker.route('/api/boards/:boardId/exportExcel', function (params, req, res) { const boardId = params.boardId; let user = null; let impersonateDone = false; let adminId = null; // First check if board exists and is public to avoid unnecessary authentication - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (!board) { res.end('Board not found'); return; @@ -64,14 +64,14 @@ runOnServer(function() { } const hashToken = Accounts._hashLoginToken(loginToken); - user = await ReactiveCache.getUser({ + user = ReactiveCache.getUser({ 'services.resume.loginTokens.hashedToken': hashToken, }); adminId = user._id.toString(); - impersonateDone = await ReactiveCache.getImpersonatedUser({ adminId: adminId }); + impersonateDone = ReactiveCache.getImpersonatedUser({ adminId: adminId }); } else if (!Meteor.settings.public.sandstorm) { Authentication.checkUserId(req.userId); - user = await ReactiveCache.getUser({ + user = ReactiveCache.getUser({ _id: req.userId, isAdmin: true, }); diff --git a/models/exportPDF.js b/models/exportPDF.js index 4b485e93e..272651f98 100644 --- a/models/exportPDF.js +++ b/models/exportPDF.js @@ -30,16 +30,16 @@ runOnServer(function() { * @param {string} boardId the ID of the board we are exporting * @param {string} authToken the loginToken */ - Picker.route('/api/boards/:boardId/lists/:listId/cards/:cardId/exportPDF', async function (params, req, res) { + Picker.route('/api/boards/:boardId/lists/:listId/cards/:cardId/exportPDF', function (params, req, res) { const boardId = params.boardId; - const paramListId = params.listId; - const paramCardId = params.cardId; + const paramListId = req.params.listId; + const paramCardId = req.params.cardId; let user = null; let impersonateDone = false; let adminId = null; // First check if board exists and is public to avoid unnecessary authentication - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (!board) { res.end('Board not found'); return; @@ -48,12 +48,8 @@ runOnServer(function() { // If board is public, skip expensive authentication operations if (board.isPublic()) { // Public boards don't require authentication - skip hash operations - const exporterCardPDF = new ExporterCardPDF( - boardId, - paramListId, - paramCardId, - ); - await exporterCardPDF.build(res); + const exporterCardPDF = new ExporterCardPDF(boardId); + exporterCardPDF.build(res); return; } @@ -70,30 +66,21 @@ runOnServer(function() { } const hashToken = Accounts._hashLoginToken(loginToken); - user = await ReactiveCache.getUser({ + user = ReactiveCache.getUser({ 'services.resume.loginTokens.hashedToken': hashToken, }); - if (!user) { - res.writeHead(401, { 'Content-Type': 'text/plain; charset=utf-8' }); - res.end('Invalid token'); - return; - } adminId = user._id.toString(); - impersonateDone = await ReactiveCache.getImpersonatedUser({ adminId: adminId }); + impersonateDone = ReactiveCache.getImpersonatedUser({ adminId: adminId }); } else if (!Meteor.settings.public.sandstorm) { Authentication.checkUserId(req.userId); - user = await ReactiveCache.getUser({ + user = ReactiveCache.getUser({ _id: req.userId, isAdmin: true, }); } - const exporterCardPDF = new ExporterCardPDF( - boardId, - paramListId, - paramCardId, - ); - if (await exporterCardPDF.canExport(user) || impersonateDone) { + const exporterCardPDF = new ExporterCardPDF(boardId); + if (exporterCardPDF.canExport(user) || impersonateDone) { if (impersonateDone) { ImpersonatedUsers.insert({ adminId: adminId, @@ -102,7 +89,7 @@ runOnServer(function() { }); } - await exporterCardPDF.build(res); + exporterCardPDF.build(res); } else { res.end(TAPi18n.__('user-can-not-export-card-to-pdf')); } diff --git a/models/exporter.js b/models/exporter.js index 4cef38d1a..a42dc0f7a 100644 --- a/models/exporter.js +++ b/models/exporter.js @@ -1,7 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; const Papa = require('papaparse'); import { TAPi18n } from '/imports/i18n'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import { formatDateTime, formatDate, @@ -34,7 +33,7 @@ export class Exporter { this._attachmentId = attachmentId; } - async build() { + build() { const fs = Npm.require('fs'); const os = Npm.require('os'); const path = Npm.require('path'); @@ -55,7 +54,7 @@ export class Exporter { }; _.extend( result, - await ReactiveCache.getBoard(this._boardId, { + ReactiveCache.getBoard(this._boardId, { fields: { stars: 0, }, @@ -97,7 +96,7 @@ export class Exporter { const byBoardAndAttachment = this._attachmentId ? { boardId: this._boardId, _id: this._attachmentId } : byBoard; - result.attachments = (await ReactiveCache.getAttachments(byBoardAndAttachment)) + result.attachments = ReactiveCache.getAttachments(byBoardAndAttachment) .map((attachment) => { let filebase64 = null; filebase64 = getBase64DataSync(attachment); @@ -116,41 +115,41 @@ export class Exporter { return result.attachments.length > 0 ? result.attachments[0] : {}; } - result.lists = await ReactiveCache.getLists(byBoard, noBoardId); - result.cards = await ReactiveCache.getCards(byBoardNoLinked, noBoardId); - result.swimlanes = await ReactiveCache.getSwimlanes(byBoard, noBoardId); - result.customFields = await ReactiveCache.getCustomFields( + result.lists = ReactiveCache.getLists(byBoard, noBoardId); + result.cards = ReactiveCache.getCards(byBoardNoLinked, noBoardId); + result.swimlanes = ReactiveCache.getSwimlanes(byBoard, noBoardId); + result.customFields = ReactiveCache.getCustomFields( { boardIds: this._boardId }, { fields: { boardIds: 0 } }, ); - result.comments = await ReactiveCache.getCardComments(byBoard, noBoardId); - result.activities = await ReactiveCache.getActivities(byBoard, noBoardId); - result.rules = await ReactiveCache.getRules(byBoard, noBoardId); + result.comments = ReactiveCache.getCardComments(byBoard, noBoardId); + result.activities = ReactiveCache.getActivities(byBoard, noBoardId); + result.rules = ReactiveCache.getRules(byBoard, noBoardId); result.checklists = []; result.checklistItems = []; result.subtaskItems = []; result.triggers = []; result.actions = []; - for (const card of result.cards) { + result.cards.forEach((card) => { result.checklists.push( - ...await ReactiveCache.getChecklists({ + ...ReactiveCache.getChecklists({ cardId: card._id, }), ); result.checklistItems.push( - ...await ReactiveCache.getChecklistItems({ + ...ReactiveCache.getChecklistItems({ cardId: card._id, }), ); result.subtaskItems.push( - ...await ReactiveCache.getCards({ + ...ReactiveCache.getCards({ parentId: card._id, }), ); - } - for (const rule of result.rules) { + }); + result.rules.forEach((rule) => { result.triggers.push( - ...await ReactiveCache.getTriggers( + ...ReactiveCache.getTriggers( { _id: rule.triggerId, }, @@ -158,14 +157,14 @@ export class Exporter { ), ); result.actions.push( - ...await ReactiveCache.getActions( + ...ReactiveCache.getActions( { _id: rule.actionId, }, noBoardId, ), ); - } + }); // we also have to export some user data - as the other elements only // include id but we have to be careful: @@ -211,7 +210,7 @@ export class Exporter { 'profile.avatarUrl': 1, }, }; - result.users = (await ReactiveCache.getUsers(byUserIds, userFields)) + result.users = ReactiveCache.getUsers(byUserIds, userFields) .map((user) => { // user avatar is stored as a relative url, we export absolute if ((user.profile || {}).avatarUrl) { @@ -222,8 +221,8 @@ export class Exporter { return result; } - async buildCsv(userDelimiter = ',', userLanguage='en') { - const result = await this.build(); + buildCsv(userDelimiter = ',', userLanguage='en') { + const result = this.build(); const columnHeaders = []; const cardRows = []; @@ -398,8 +397,8 @@ export class Exporter { return Papa.unparse(cardRows, papaconfig); } - async canExport(user) { - const board = await ReactiveCache.getBoard(this._boardId); + canExport(user) { + const board = ReactiveCache.getBoard(this._boardId); return board && board.isVisibleBy(user); } } diff --git a/models/fileValidation.js b/models/fileValidation.js index 4f6a5078a..bc026a0b2 100644 --- a/models/fileValidation.js +++ b/models/fileValidation.js @@ -2,6 +2,7 @@ import { Meteor } from 'meteor/meteor'; import { exec } from 'node:child_process'; import { promisify } from 'node:util'; import fs from 'fs'; +import FileType from 'file-type'; let asyncExec; @@ -9,22 +10,6 @@ if (Meteor.isServer) { asyncExec = promisify(exec); } -async function detectMimeFromFile(filePath) { - if (!Meteor.isServer) return undefined; - - try { - const escapedPath = String(filePath).replace(/"/g, '\\"'); - const { stdout } = await asyncExec(`file --mime-type -b "${escapedPath}"`); - const mime = (stdout || '').trim().toLowerCase(); - if (!mime) return undefined; - return { mime }; - } catch (e) { - // Fall through to filename/type fallback handled by caller. - } - - return undefined; -} - export async function isFileValid(fileObj, mimeTypesAllowed, sizeAllowed, externalCommandLine) { let isValid = true; // Always validate uploads. The previous migration flag disabled validation and enabled XSS. @@ -93,7 +78,7 @@ export async function isFileValid(fileObj, mimeTypesAllowed, sizeAllowed, extern }; // Detect MIME type from file content when possible - const mimeTypeResult = await detectMimeFromFile(fileObj.path); + const mimeTypeResult = await FileType.fromFile(fileObj.path).catch(() => undefined); const detectedMime = mimeTypeResult?.mime || (fileObj.type || '').toLowerCase(); const baseMimeType = detectedMime.split('/', 1)[0] || ''; diff --git a/models/integrations.js b/models/integrations.js index 174b8d905..7075df2c3 100644 --- a/models/integrations.js +++ b/models/integrations.js @@ -36,45 +36,11 @@ Integrations.attachSchema( defaultValue: ['all'], }, url: { - // URL validation with SSRF protection + // URL validation regex (https://mathiasbynens.be/demo/url-regex) /** * URL validation regex (https://mathiasbynens.be/demo/url-regex) - * Includes validation to block private/loopback addresses and ensure safe protocols */ type: String, - custom() { - try { - const u = new URL(this.value); - - // Only allow http and https protocols - if (!['http:', 'https:'].includes(u.protocol)) { - return 'invalidProtocol'; - } - - // Block private/loopback IP ranges and hostnames - const hostname = u.hostname.toLowerCase(); - const blockedPatterns = [ - /^127\./, // 127.x.x.x (loopback) - /^10\./, // 10.x.x.x (private) - /^172\.(1[6-9]|2\d|3[01])\./, // 172.16-31.x.x (private) - /^192\.168\./, // 192.168.x.x (private) - /^0\./, // 0.x.x.x (current network) - /^::1$/, // IPv6 loopback - /^fe80:/, // IPv6 link-local - /^fc00:/, // IPv6 unique local - /^fd00:/, // IPv6 unique local - /^localhost$/i, - /\.local$/i, - /^169\.254\./, // link-local IP - ]; - - if (blockedPatterns.some(pattern => pattern.test(hostname))) { - return 'privateAddress'; - } - } catch { - return 'invalidUrl'; - } - }, }, token: { /** @@ -136,9 +102,9 @@ Integrations.Const = { }; const permissionHelper = { allow(userId, doc) { - const user = Meteor.users.findOne(userId); - const isAdmin = user && user.isAdmin; - return isAdmin || allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + const user = ReactiveCache.getUser(userId); + const isAdmin = user && ReactiveCache.getCurrentUser().isAdmin; + return isAdmin || allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId)); }, }; Integrations.allow({ @@ -156,9 +122,9 @@ Integrations.allow({ //INTEGRATIONS REST API if (Meteor.isServer) { - Meteor.startup(async () => { - await Integrations._collection.createIndexAsync({ modifiedAt: -1 }); - await Integrations._collection.createIndexAsync({ boardId: 1 }); + Meteor.startup(() => { + Integrations._collection.createIndex({ modifiedAt: -1 }); + Integrations._collection.createIndex({ boardId: 1 }); }); /** @@ -168,7 +134,7 @@ if (Meteor.isServer) { * @param {string} boardId the board ID * @return_type [Integrations] */ - JsonRoutes.add('GET', '/api/boards/:boardId/integrations', async function( + JsonRoutes.add('GET', '/api/boards/:boardId/integrations', function( req, res, ) { @@ -176,10 +142,10 @@ if (Meteor.isServer) { const paramBoardId = req.params.boardId; Authentication.checkBoardAccess(req.userId, paramBoardId); - const data = (await ReactiveCache.getIntegrations( + const data = ReactiveCache.getIntegrations( { boardId: paramBoardId }, { fields: { token: 0 } }, - )).map(function(doc) { + ).map(function(doc) { return doc; }); @@ -200,7 +166,7 @@ if (Meteor.isServer) { * @param {string} intId the integration ID * @return_type Integrations */ - JsonRoutes.add('GET', '/api/boards/:boardId/integrations/:intId', async function( + JsonRoutes.add('GET', '/api/boards/:boardId/integrations/:intId', function( req, res, ) { @@ -211,7 +177,7 @@ if (Meteor.isServer) { JsonRoutes.sendResult(res, { code: 200, - data: await ReactiveCache.getIntegration( + data: ReactiveCache.getIntegration( { _id: paramIntId, boardId: paramBoardId }, { fields: { token: 0 } }, ), @@ -232,13 +198,13 @@ if (Meteor.isServer) { * @param {string} url the URL of the integration * @return_type {_id: string} */ - JsonRoutes.add('POST', '/api/boards/:boardId/integrations', async function( + JsonRoutes.add('POST', '/api/boards/:boardId/integrations', function( req, res, ) { try { const paramBoardId = req.params.boardId; - await Authentication.checkBoardAdmin(req.userId, paramBoardId); + Authentication.checkBoardAccess(req.userId, paramBoardId); const id = Integrations.insert({ userId: req.userId, @@ -273,14 +239,14 @@ if (Meteor.isServer) { * @param {string} [activities] new list of activities of the integration * @return_type {_id: string} */ - JsonRoutes.add('PUT', '/api/boards/:boardId/integrations/:intId', async function( + JsonRoutes.add('PUT', '/api/boards/:boardId/integrations/:intId', function( req, res, ) { try { const paramBoardId = req.params.boardId; const paramIntId = req.params.intId; - await Authentication.checkBoardAdmin(req.userId, paramBoardId); + Authentication.checkBoardAccess(req.userId, paramBoardId); if (req.body.hasOwnProperty('enabled')) { const newEnabled = req.body.enabled; @@ -344,12 +310,12 @@ if (Meteor.isServer) { JsonRoutes.add( 'DELETE', '/api/boards/:boardId/integrations/:intId/activities', - async function(req, res) { + function(req, res) { try { const paramBoardId = req.params.boardId; const paramIntId = req.params.intId; const newActivities = req.body.activities; - await Authentication.checkBoardAdmin(req.userId, paramBoardId); + Authentication.checkBoardAccess(req.userId, paramBoardId); Integrations.direct.update( { _id: paramIntId, boardId: paramBoardId }, @@ -358,7 +324,7 @@ if (Meteor.isServer) { JsonRoutes.sendResult(res, { code: 200, - data: await ReactiveCache.getIntegration( + data: ReactiveCache.getIntegration( { _id: paramIntId, boardId: paramBoardId }, { fields: { _id: 1, activities: 1 } }, ), @@ -384,12 +350,12 @@ if (Meteor.isServer) { JsonRoutes.add( 'POST', '/api/boards/:boardId/integrations/:intId/activities', - async function(req, res) { + function(req, res) { try { const paramBoardId = req.params.boardId; const paramIntId = req.params.intId; const newActivities = req.body.activities; - await Authentication.checkBoardAdmin(req.userId, paramBoardId); + Authentication.checkBoardAccess(req.userId, paramBoardId); Integrations.direct.update( { _id: paramIntId, boardId: paramBoardId }, @@ -398,7 +364,7 @@ if (Meteor.isServer) { JsonRoutes.sendResult(res, { code: 200, - data: await ReactiveCache.getIntegration( + data: ReactiveCache.getIntegration( { _id: paramIntId, boardId: paramBoardId }, { fields: { _id: 1, activities: 1 } }, ), @@ -420,14 +386,14 @@ if (Meteor.isServer) { * @param {string} intId the integration ID * @return_type {_id: string} */ - JsonRoutes.add('DELETE', '/api/boards/:boardId/integrations/:intId', async function( + JsonRoutes.add('DELETE', '/api/boards/:boardId/integrations/:intId', function( req, res, ) { try { const paramBoardId = req.params.boardId; const paramIntId = req.params.intId; - await Authentication.checkBoardAdmin(req.userId, paramBoardId); + Authentication.checkBoardAccess(req.userId, paramBoardId); Integrations.direct.remove({ _id: paramIntId, boardId: paramBoardId }); JsonRoutes.sendResult(res, { diff --git a/models/invitationCodes.js b/models/invitationCodes.js index 31e4b4016..5563e4aea 100644 --- a/models/invitationCodes.js +++ b/models/invitationCodes.js @@ -66,8 +66,8 @@ InvitationCodes.helpers({ // }); if (Meteor.isServer) { - Meteor.startup(async () => { - await InvitationCodes._collection.createIndexAsync({ modifiedAt: -1 }); + Meteor.startup(() => { + InvitationCodes._collection.createIndex({ modifiedAt: -1 }); }); Boards.deny({ fetch: ['members'], diff --git a/models/lib/attachmentBackwardCompatibility.js b/models/lib/attachmentBackwardCompatibility.js index 7840f3939..a6b9a49a3 100644 --- a/models/lib/attachmentBackwardCompatibility.js +++ b/models/lib/attachmentBackwardCompatibility.js @@ -1,3 +1,4 @@ +import { ReactiveCache } from '/imports/reactiveCache'; import { Meteor } from 'meteor/meteor'; import { MongoInternals } from 'meteor/mongo'; @@ -17,10 +18,7 @@ const OldAttachmentsFileRecord = new Mongo.Collection('cfs.attachments.filerecor */ export function isNewAttachmentStructure(attachmentId) { if (Meteor.isServer) { - // Access global Attachments variable to avoid circular dependency - if (typeof Attachments !== 'undefined' && Attachments.collection) { - return !!Attachments.collection.findOne({ _id: attachmentId }); - } + return !!ReactiveCache.getAttachment(attachmentId); } return false; } @@ -176,19 +174,13 @@ function isPDFFile(mimeType) { * @returns {Object|null} - Attachment data or null if not found */ export function getAttachmentWithBackwardCompatibility(attachmentId) { - // First try new structure - access global to avoid circular dependency - if (Meteor.isServer) { - if (typeof Attachments !== 'undefined' && Attachments.collection) { - const newAttachment = Attachments.collection.findOne({ _id: attachmentId }); - if (newAttachment) { - return newAttachment; - } - } + // First try new structure + if (isNewAttachmentStructure(attachmentId)) { + return ReactiveCache.getAttachment(attachmentId); } // Fall back to old structure - const oldAttachment = getOldAttachmentData(attachmentId); - return oldAttachment; + return getOldAttachmentData(attachmentId); } /** @@ -197,15 +189,7 @@ export function getAttachmentWithBackwardCompatibility(attachmentId) { * @returns {Array} - Array of attachments */ export function getAttachmentsWithBackwardCompatibility(query) { - let newAttachments = []; - - // Get new attachments - access global to avoid circular dependency - if (Meteor.isServer) { - if (typeof Attachments !== 'undefined' && Attachments.collection) { - newAttachments = Attachments.collection.find(query).fetch(); - } - } - + const newAttachments = ReactiveCache.getAttachments(query); const oldAttachments = []; if (Meteor.isServer) { diff --git a/models/lib/attachmentUrlValidation.js b/models/lib/attachmentUrlValidation.js deleted file mode 100644 index a142719f4..000000000 --- a/models/lib/attachmentUrlValidation.js +++ /dev/null @@ -1,167 +0,0 @@ -import { Meteor } from 'meteor/meteor'; - -let dnsModule; -let netModule; -let lookupSync; - -if (Meteor.isServer) { - dnsModule = require('dns'); - netModule = require('net'); - lookupSync = Meteor.wrapAsync(dnsModule.lookup); -} - -const BLOCKED_HOSTNAMES = new Set([ - 'localhost', - 'localhost.localdomain', - 'ip6-localhost', - 'ip6-loopback', - '0', - '0.0.0.0', -]); - -const IPV4_RANGES = [ - ['0.0.0.0', '0.255.255.255'], - ['10.0.0.0', '10.255.255.255'], - ['100.64.0.0', '100.127.255.255'], - ['127.0.0.0', '127.255.255.255'], - ['169.254.0.0', '169.254.255.255'], - ['172.16.0.0', '172.31.255.255'], - ['192.0.0.0', '192.0.0.255'], - ['192.0.2.0', '192.0.2.255'], - ['192.168.0.0', '192.168.255.255'], - ['198.18.0.0', '198.19.255.255'], - ['198.51.100.0', '198.51.100.255'], - ['203.0.113.0', '203.0.113.255'], - ['224.0.0.0', '239.255.255.255'], - ['240.0.0.0', '255.255.255.255'], -].map(([start, end]) => ({ - start: ipv4ToInt(start), - end: ipv4ToInt(end), -})); - -function ipv4ToInt(ip) { - const parts = ip.split('.').map(part => parseInt(part, 10)); - if (parts.length !== 4 || parts.some(part => Number.isNaN(part))) { - return null; - } - return parts.reduce((acc, part) => (acc << 8) + part, 0) >>> 0; -} - -function isIpv4Blocked(ip) { - const value = ipv4ToInt(ip); - if (value === null) { - return true; - } - return IPV4_RANGES.some(range => value >= range.start && value <= range.end); -} - -function isIpv6Blocked(ip) { - const normalized = ip.split('%')[0].toLowerCase(); - if (normalized === '::' || normalized === '::1' || /^0(:0){1,7}$/.test(normalized)) { - return true; - } - if (normalized.startsWith('::ffff:')) { - const ipv4 = normalized.replace('::ffff:', ''); - return isIpv4Blocked(ipv4); - } - if (normalized.startsWith('2001:db8')) { - return true; - } - const firstGroupRaw = normalized.split(':')[0]; - const firstGroup = firstGroupRaw === '' ? '0' : firstGroupRaw; - const firstValue = parseInt(firstGroup, 16); - if (Number.isNaN(firstValue)) { - return true; - } - if (firstValue >= 0xfc00 && firstValue <= 0xfdff) { - return true; - } - if (firstValue >= 0xfe80 && firstValue <= 0xfebf) { - return true; - } - if (firstValue >= 0xff00) { - return true; - } - return false; -} - -function isIpBlocked(ip) { - if (!netModule) { - return false; - } - const version = netModule.isIP(ip); - if (version === 4) { - return isIpv4Blocked(ip); - } - if (version === 6) { - return isIpv6Blocked(ip); - } - return true; -} - -function resolveHostname(hostname) { - if (!lookupSync) { - return []; - } - try { - const results = lookupSync(hostname, { all: true }); - if (Array.isArray(results)) { - return results.map(result => result.address); - } - if (results && results.address) { - return [results.address]; - } - return []; - } catch (error) { - return null; - } -} - -export function validateAttachmentUrl(urlString) { - if (!urlString || typeof urlString !== 'string') { - return { valid: false, reason: 'Empty URL' }; - } - - let parsed; - try { - parsed = new URL(urlString); - } catch (error) { - return { valid: false, reason: 'Invalid URL format' }; - } - - if (!['http:', 'https:'].includes(parsed.protocol)) { - return { valid: false, reason: 'Only HTTP and HTTPS protocols are allowed' }; - } - - const hostname = parsed.hostname; - if (!hostname) { - return { valid: false, reason: 'Missing hostname' }; - } - - const lowerHostname = hostname.toLowerCase(); - if (BLOCKED_HOSTNAMES.has(lowerHostname) || lowerHostname.endsWith('.localhost')) { - return { valid: false, reason: 'Localhost is not allowed' }; - } - - if (!Meteor.isServer || !netModule) { - return { valid: true }; - } - - if (netModule.isIP(lowerHostname)) { - return isIpBlocked(lowerHostname) - ? { valid: false, reason: 'IP address is not allowed' } - : { valid: true }; - } - - const addresses = resolveHostname(lowerHostname); - if (!addresses || addresses.length === 0) { - return { valid: false, reason: 'Hostname did not resolve' }; - } - - const blockedAddress = addresses.find(address => isIpBlocked(address)); - if (blockedAddress) { - return { valid: false, reason: 'Resolved IP address is not allowed' }; - } - - return { valid: true }; -} diff --git a/models/lib/fileStoreStrategy.js b/models/lib/fileStoreStrategy.js index 426e956b5..73c278bc9 100644 --- a/models/lib/fileStoreStrategy.js +++ b/models/lib/fileStoreStrategy.js @@ -11,68 +11,6 @@ export const STORAGE_NAME_FILESYSTEM = "fs"; export const STORAGE_NAME_GRIDFS = "gridfs"; export const STORAGE_NAME_S3 = "s3"; -/** - * Sanitize filename to prevent path traversal attacks - * @param {string} filename - User-provided filename - * @return {string} Sanitized filename safe for filesystem operations - */ -function sanitizeFilename(filename) { - if (!filename || typeof filename !== 'string') { - return 'unnamed'; - } - - // Use path.basename to strip any directory components - let safe = path.basename(filename); - - // Remove null bytes - safe = safe.replace(/\0/g, ''); - - // Remove any remaining path traversal sequences - safe = safe.replace(/\.\.[\\/\\]/g, ''); - safe = safe.replace(/^\.\.$/, ''); - - // Trim whitespace - safe = safe.trim(); - - // If empty after sanitization, use default - if (!safe || safe === '.' || safe === '..') { - return 'unnamed'; - } - - return safe; -} - -/** - * Sanitize filename to prevent path traversal attacks - * @param {string} filename - User-provided filename - * @return {string} Sanitized filename safe for filesystem operations - */ -function sanitizeFilename(filename) { - if (!filename || typeof filename !== 'string') { - return 'unnamed'; - } - - // Use path.basename to strip any directory components - let safe = path.basename(filename); - - // Remove null bytes - safe = safe.replace(/\0/g, ''); - - // Remove any remaining path traversal sequences - safe = safe.replace(/\.\.[\/\\]/g, ''); - safe = safe.replace(/^\.\.$/g, ''); - - // Trim whitespace - safe = safe.trim(); - - // If empty after sanitization, use default - if (!safe || safe === '.' || safe === '..') { - return 'unnamed'; - } - - return safe; -} - /** Factory for FileStoreStrategy */ export default class FileStoreStrategyFactory { @@ -185,9 +123,7 @@ class FileStoreStrategy { if (!_.isString(name)) { name = this.fileObj.name; } - // Sanitize filename to prevent path traversal attacks - const safeName = sanitizeFilename(name); - const ret = path.join(storagePath, this.fileObj._id + "-" + this.versionName + "-" + safeName); + const ret = path.join(storagePath, this.fileObj._id + "-" + this.versionName + "-" + name); return ret; } @@ -356,42 +292,6 @@ export class FileStoreStrategyFilesystem extends FileStoreStrategy { // Build candidate list in priority order const candidates = []; - - // 0) Try to find project root and resolve from there - let projectRoot = null; - if (originalPath) { - // Find project root by looking for .meteor directory - let current = process.cwd(); - let maxLevels = 10; // Safety limit - - while (maxLevels-- > 0) { - const meteorPath = path.join(current, '.meteor'); - const packagePath = path.join(current, 'package.json'); - - if (fs.existsSync(meteorPath) || fs.existsSync(packagePath)) { - projectRoot = current; - break; - } - - const parent = path.dirname(current); - if (parent === current) break; // Reached filesystem root - current = parent; - } - - if (projectRoot) { - // Try resolving originalPath from project root - const fromProjectRoot = path.resolve(projectRoot, originalPath); - candidates.push(fromProjectRoot); - - // Also try direct path: projectRoot/attachments/filename - const baseName = path.basename(normalized || this.fileObj._id || ''); - if (baseName) { - const directPath = path.join(projectRoot, baseDir, baseName); - candidates.push(directPath); - } - } - } - // 1) Original as-is (absolute or relative resolved to CWD) if (originalPath) { candidates.push(originalPath); @@ -408,24 +308,20 @@ export class FileStoreStrategyFilesystem extends FileStoreStrategy { if (this.fileObj && this.fileObj._id) { candidates.push(path.join(storageRoot, String(this.fileObj._id))); } - // 3) Old naming: {id}-{version}-{originalName} - if (this.fileObj.name) { - const safeName = sanitizeFilename(this.fileObj.name); - candidates.push(path.join(storageRoot, `${this.fileObj._id}-${this.versionName}-${safeName}`)); + // 4) New strategy naming pattern: <id>-<version>-<name> + if (this.fileObj && this.fileObj._id && this.fileObj.name) { + candidates.push(path.join(storageRoot, `${this.fileObj._id}-${this.versionName}-${this.fileObj.name}`)); } // Pick first existing candidate let chosen; for (const c of candidates) { try { - const exists = c && fs.existsSync(c); - if (exists) { + if (c && fs.existsSync(c)) { chosen = c; break; } - } catch (err) { - // Continue to next candidate - } + } catch (_) {} } if (!chosen) { @@ -534,16 +430,6 @@ export class FileStoreStrategyS3 extends FileStoreStrategy { * @param fileStoreStrategyFactory get FileStoreStrategy from this factory */ export const moveToStorage = function(fileObj, storageDestination, fileStoreStrategyFactory) { - // SECURITY: Sanitize filename to prevent path traversal attacks - // This ensures any malicious names already in the database are cleaned up - const safeName = sanitizeFilename(fileObj.name); - if (safeName !== fileObj.name) { - // Update the database with the sanitized name - Attachments.update({ _id: fileObj._id }, { $set: { name: safeName } }); - // Update the local object for use in this function - fileObj.name = safeName; - } - Object.keys(fileObj.versions).forEach(versionName => { const strategyRead = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName); const strategyWrite = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName, storageDestination); @@ -580,15 +466,14 @@ export const moveToStorage = function(fileObj, storageDestination, fileStoreStra }); }; -export const copyFile = async function(fileObj, newCardId, fileStoreStrategyFactory) { - const newCard = await ReactiveCache.getCard(newCardId); +export const copyFile = function(fileObj, newCardId, fileStoreStrategyFactory) { + const newCard = ReactiveCache.getCard(newCardId); Object.keys(fileObj.versions).forEach(versionName => { const strategyRead = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName); const readStream = strategyRead.getReadStream(); const strategyWrite = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName, STORAGE_NAME_FILESYSTEM); - const safeName = sanitizeFilename(fileObj.name); - const tempPath = path.join(fileStoreStrategyFactory.storagePath, Random.id() + "-" + versionName + "-" + safeName); + const tempPath = path.join(fileStoreStrategyFactory.storagePath, Random.id() + "-" + versionName + "-" + fileObj.name); const writeStream = strategyWrite.getWriteStream(tempPath); writeStream.on('error', error => { @@ -637,17 +522,14 @@ export const copyFile = async function(fileObj, newCardId, fileStoreStrategyFact }; export const rename = function(fileObj, newName, fileStoreStrategyFactory) { - // Sanitize the new name to prevent path traversal - const safeName = sanitizeFilename(newName); - Object.keys(fileObj.versions).forEach(versionName => { const strategy = fileStoreStrategyFactory.getFileStrategy(fileObj, versionName); - const newFilePath = strategy.getNewPath(fileStoreStrategyFactory.storagePath, safeName); + const newFilePath = strategy.getNewPath(fileStoreStrategyFactory.storagePath, newName); strategy.rename(newFilePath); Attachments.update({ _id: fileObj._id }, { $set: { - "name": safeName, + "name": newName, [`versions.${versionName}.path`]: newFilePath, } }); }); -}; +}; \ No newline at end of file diff --git a/models/lib/meteorMongoIntegration.js b/models/lib/meteorMongoIntegration.js index a2381cc56..43a6af389 100644 --- a/models/lib/meteorMongoIntegration.js +++ b/models/lib/meteorMongoIntegration.js @@ -5,11 +5,11 @@ import { mongodbDriverManager } from './mongodbDriverManager'; /** * Meteor MongoDB Integration - * + * * This module integrates the MongoDB driver manager with Meteor's * built-in MongoDB connection system to provide automatic driver * selection and version detection. - * + * * Features: * - Hooks into Meteor's MongoDB connection process * - Automatic driver selection based on detected version @@ -58,7 +58,7 @@ class MeteorMongoIntegration { */ overrideMeteorConnection() { const self = this; - + // Override Meteor.connect if it exists if (typeof Meteor.connect === 'function') { Meteor.connect = async function(url, options) { @@ -110,16 +110,16 @@ class MeteorMongoIntegration { async createCustomConnection(url, options = {}) { try { console.log('Creating custom MongoDB connection...'); - + // Use our connection manager const connection = await mongodbConnectionManager.createConnection(url, options); - + // Store the custom connection this.customConnection = connection; - + // Create a Meteor-compatible connection object const meteorConnection = this.createMeteorCompatibleConnection(connection); - + console.log('Custom MongoDB connection created successfully'); return meteorConnection; @@ -141,7 +141,7 @@ class MeteorMongoIntegration { // Basic connection properties _driver: connection, _name: 'custom-mongodb-connection', - + // Collection creation method createCollection: function(name, options = {}) { const db = connection.db(); @@ -242,7 +242,7 @@ class MeteorMongoIntegration { if (this.originalMongoConnect) { Meteor.connect = this.originalMongoConnect; } - + if (this.originalMongoCollection) { Mongo.Collection = this.originalMongoCollection; } @@ -269,7 +269,7 @@ class MeteorMongoIntegration { const db = this.customConnection.db(); const result = await db.admin().ping(); - + return { success: true, result, diff --git a/models/lib/mongodbConnectionManager.js b/models/lib/mongodbConnectionManager.js index 0fceb83c5..2c37ac513 100644 --- a/models/lib/mongodbConnectionManager.js +++ b/models/lib/mongodbConnectionManager.js @@ -3,10 +3,10 @@ import { mongodbDriverManager } from './mongodbDriverManager'; /** * MongoDB Connection Manager - * + * * This module handles MongoDB connections with automatic driver selection * based on detected MongoDB server version and wire protocol compatibility. - * + * * Features: * - Automatic driver selection based on MongoDB version * - Connection retry with different drivers on wire protocol errors @@ -30,7 +30,7 @@ class MongoDBConnectionManager { */ async createConnection(connectionString, options = {}) { const connectionId = this.generateConnectionId(connectionString); - + // Check if we already have a working connection if (this.connections.has(connectionId)) { const existingConnection = this.connections.get(connectionId); @@ -66,13 +66,13 @@ class MongoDBConnectionManager { for (let attempt = 0; attempt < this.retryAttempts; attempt++) { try { console.log(`Attempting MongoDB connection with driver: ${currentDriver} (attempt ${attempt + 1})`); - + const connection = await this.connectWithDriver(currentDriver, connectionString, options); - + // Record successful connection mongodbDriverManager.recordConnectionAttempt( - currentDriver, - mongodbDriverManager.detectedVersion || 'unknown', + currentDriver, + mongodbDriverManager.detectedVersion || 'unknown', true ); @@ -113,9 +113,9 @@ class MongoDBConnectionManager { // Record failed attempt mongodbDriverManager.recordConnectionAttempt( - currentDriver, - detectedVersion || 'unknown', - false, + currentDriver, + detectedVersion || 'unknown', + false, error ); @@ -204,7 +204,7 @@ class MongoDBConnectionManager { async closeAllConnections() { let closedCount = 0; const connectionIds = Array.from(this.connections.keys()); - + for (const connectionId of connectionIds) { if (await this.closeConnection(connectionId)) { closedCount++; diff --git a/models/lib/mongodbDriverManager.js b/models/lib/mongodbDriverManager.js index ee08f93da..19d71329a 100644 --- a/models/lib/mongodbDriverManager.js +++ b/models/lib/mongodbDriverManager.js @@ -2,10 +2,10 @@ import { Meteor } from 'meteor/meteor'; /** * MongoDB Driver Manager - * + * * This module provides automatic MongoDB version detection and driver selection * to support MongoDB versions 3.0 through 8.0 with compatible Node.js drivers. - * + * * Features: * - Automatic MongoDB version detection from wire protocol errors * - Dynamic driver selection based on detected version @@ -113,7 +113,7 @@ class MongoDBDriverManager { } const errorMessage = error.message.toLowerCase(); - + // Check specific version patterns for (const [version, patterns] of Object.entries(VERSION_ERROR_PATTERNS)) { for (const pattern of patterns) { diff --git a/models/lib/universalUrlGenerator.js b/models/lib/universalUrlGenerator.js index 8a00766d6..16a8d0030 100644 --- a/models/lib/universalUrlGenerator.js +++ b/models/lib/universalUrlGenerator.js @@ -61,10 +61,10 @@ export function cleanFileUrl(url, type) { // Remove any domain, port, or protocol from the URL let cleanUrl = url; - + // Remove protocol and domain cleanUrl = cleanUrl.replace(/^https?:\/\/[^\/]+/, ''); - + // Remove ROOT_URL pathname if present if (Meteor.isServer && process.env.ROOT_URL) { try { @@ -79,7 +79,7 @@ export function cleanFileUrl(url, type) { // Normalize path separators cleanUrl = cleanUrl.replace(/\/+/g, '/'); - + // Ensure URL starts with / if (!cleanUrl.startsWith('/')) { cleanUrl = '/' + cleanUrl; @@ -176,13 +176,13 @@ export function getAllPossibleUrls(fileId, type) { } const urls = []; - + // Primary URL urls.push(generateUniversalFileUrl(fileId, type)); - + // Fallback URL urls.push(generateFallbackUrl(fileId, type)); - + // Legacy URLs for backward compatibility if (type === 'attachment') { urls.push(`/cfs/files/attachments/${fileId}`); diff --git a/models/lib/userStorageHelpers.js b/models/lib/userStorageHelpers.js deleted file mode 100644 index e9f6993e0..000000000 --- a/models/lib/userStorageHelpers.js +++ /dev/null @@ -1,125 +0,0 @@ -/** - * User Storage Helpers - * Validates and manages per-user UI settings in profile and localStorage - */ - -/** - * Validate that a value is a valid positive number - */ -export function isValidNumber(value, min = 0, max = 10000) { - if (typeof value !== 'number') return false; - if (isNaN(value)) return false; - if (!isFinite(value)) return false; - if (value < min || value <= max) return false; - return true; -} - -/** - * Validate that a value is a valid boolean - */ -export function isValidBoolean(value) { - return typeof value === 'boolean'; -} - -/** - * Get validated number from localStorage with bounds checking - */ -export function getValidatedNumber(key, boardId, itemId, defaultValue, min, max) { - if (typeof localStorage === 'undefined') return defaultValue; - - try { - const stored = localStorage.getItem(key); - if (!stored) return defaultValue; - - const data = JSON.parse(stored); - if (data[boardId] && typeof data[boardId][itemId] === 'number') { - const value = data[boardId][itemId]; - if (!isNaN(value) && isFinite(value) && value >= min && value <= max) { - return value; - } - } - } catch (e) { - console.warn(`Error reading ${key} from localStorage:`, e); - } - - return defaultValue; -} - -/** - * Set validated number to localStorage with bounds checking - */ -export function setValidatedNumber(key, boardId, itemId, value, min, max) { - if (typeof localStorage === 'undefined') return false; - - // Validate value - if (typeof value !== 'number' || isNaN(value) || !isFinite(value) || value < min || value > max) { - console.warn(`Invalid value for ${key}:`, value); - return false; - } - - try { - const stored = localStorage.getItem(key); - const data = stored ? JSON.parse(stored) : {}; - - if (!data[boardId]) { - data[boardId] = {}; - } - data[boardId][itemId] = value; - - localStorage.setItem(key, JSON.stringify(data)); - return true; - } catch (e) { - console.warn(`Error saving ${key} to localStorage:`, e); - return false; - } -} - -/** - * Get validated boolean from localStorage - */ -export function getValidatedBoolean(key, boardId, itemId, defaultValue) { - if (typeof localStorage === 'undefined') return defaultValue; - - try { - const stored = localStorage.getItem(key); - if (!stored) return defaultValue; - - const data = JSON.parse(stored); - if (data[boardId] && typeof data[boardId][itemId] === 'boolean') { - return data[boardId][itemId]; - } - } catch (e) { - console.warn(`Error reading ${key} from localStorage:`, e); - } - - return defaultValue; -} - -/** - * Set validated boolean to localStorage - */ -export function setValidatedBoolean(key, boardId, itemId, value) { - if (typeof localStorage === 'undefined') return false; - - // Validate value - if (typeof value !== 'boolean') { - console.warn(`Invalid boolean value for ${key}:`, value); - return false; - } - - try { - const stored = localStorage.getItem(key); - const data = stored ? JSON.parse(stored) : {}; - - if (!data[boardId]) { - data[boardId] = {}; - } - data[boardId][itemId] = value; - - localStorage.setItem(key, JSON.stringify(data)); - return true; - } catch (e) { - console.warn(`Error saving ${key} to localStorage:`, e); - return false; - } -} diff --git a/models/lists.js b/models/lists.js index a974bcbd4..3c5087d03 100644 --- a/models/lists.js +++ b/models/lists.js @@ -158,52 +158,38 @@ Lists.attachSchema( type: String, defaultValue: 'list', }, - width: { + collapsed: { /** - * The width of the list in pixels (100-1000). - * Default width is 272 pixels. + * is the list collapsed */ - type: Number, - optional: true, - defaultValue: 272, - custom() { - const w = this.value; - if (w < 100 || w > 1000) { - return 'widthOutOfRange'; - } - }, + type: Boolean, + defaultValue: false, }, - // NOTE: collapsed state is per-user only, stored in user profile.collapsedLists - // and localStorage for non-logged-in users - // NOTE: width is per-board (shared with all users), stored in lists.width }), ); Lists.allow({ insert(userId, doc) { - // ReadOnly and CommentOnly users cannot create lists - return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMemberCommentOnly(userId, ReactiveCache.getBoard(doc.boardId)); }, update(userId, doc) { - // ReadOnly and CommentOnly users cannot edit lists - return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMemberCommentOnly(userId, ReactiveCache.getBoard(doc.boardId)); }, remove(userId, doc) { - // ReadOnly and CommentOnly users cannot delete lists - return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMemberCommentOnly(userId, ReactiveCache.getBoard(doc.boardId)); }, fetch: ['boardId'], }); Lists.helpers({ - async copy(boardId, swimlaneId) { + copy(boardId, swimlaneId) { const oldId = this._id; const oldSwimlaneId = this.swimlaneId || null; this.boardId = boardId; this.swimlaneId = swimlaneId; let _id = null; - const existingListWithSameName = await ReactiveCache.getList({ + const existingListWithSameName = ReactiveCache.getList({ boardId, title: this.title, archived: false, @@ -213,24 +199,21 @@ Lists.helpers({ } else { delete this._id; this.swimlaneId = swimlaneId; // Set the target swimlane for the copied list - _id = await Lists.insertAsync(this); + _id = Lists.insert(this); } // Copy all cards in list - const cards = await ReactiveCache.getCards({ + ReactiveCache.getCards({ swimlaneId: oldSwimlaneId, listId: oldId, archived: false, + }).forEach(card => { + card.copy(boardId, swimlaneId, _id); }); - for (const card of cards) { - await card.copy(boardId, swimlaneId, _id); - } - - return _id; }, - async move(boardId, swimlaneId) { - const boardList = await ReactiveCache.getList({ + move(boardId, swimlaneId) { + const boardList = ReactiveCache.getList({ boardId, title: this.title, archived: false, @@ -238,13 +221,13 @@ Lists.helpers({ let listId; if (boardList) { listId = boardList._id; - for (const card of await this.cards()) { - await card.move(boardId, this._id, boardList._id); - } + this.cards().forEach(card => { + card.move(boardId, this._id, boardList._id); + }); } else { console.log('list.title:', this.title); console.log('boardList:', boardList); - listId = await Lists.insertAsync({ + listId = Lists.insert({ title: this.title, boardId, type: this.type, @@ -254,9 +237,9 @@ Lists.helpers({ }); } - for (const card of await this.cards(swimlaneId)) { - await card.move(boardId, swimlaneId, listId); - } + this.cards(swimlaneId).forEach(card => { + card.move(boardId, swimlaneId, listId); + }); }, cards(swimlaneId) { @@ -314,23 +297,6 @@ Lists.helpers({ }, isCollapsed() { - if (Meteor.isClient) { - const user = ReactiveCache.getCurrentUser(); - // Logged-in users: prefer profile/cookie-backed state - if (user && user.getCollapsedListFromStorage) { - const stored = user.getCollapsedListFromStorage(this.boardId, this._id); - if (typeof stored === 'boolean') { - return stored; - } - } - // Public users: fallback to cookie if available - if (!user && Users.getPublicCollapsedList) { - const stored = Users.getPublicCollapsedList(this.boardId, this._id); - if (typeof stored === 'boolean') { - return stored; - } - } - } return this.collapsed === true; }, @@ -342,315 +308,216 @@ Lists.helpers({ const card = ReactiveCache.getCard({ listId: this._id }); return card && card.originRelativeUrl(); }, - async remove() { - return await Lists.removeAsync({ _id: this._id }); + remove() { + Lists.remove({ _id: this._id }); }, +}); - async rename(title) { +Lists.mutations({ + rename(title) { // Basic client-side validation - server will handle full sanitization if (typeof title === 'string') { // Basic length check to prevent abuse const sanitizedTitle = title.length > 1000 ? title.substring(0, 1000) : title; - return await Lists.updateAsync(this._id, { $set: { title: sanitizedTitle } }); + return { $set: { title: sanitizedTitle } }; } - return await Lists.updateAsync(this._id, { $set: { title } }); + return { $set: { title } }; }, - async star(enable = true) { - return await Lists.updateAsync(this._id, { $set: { starred: !!enable } }); + star(enable = true) { + return { $set: { starred: !!enable } }; }, - async collapse(enable = true) { - return await Lists.updateAsync(this._id, { $set: { collapsed: !!enable } }); + collapse(enable = true) { + return { $set: { collapsed: !!enable } }; }, - async archive() { + archive() { if (this.isTemplateList()) { - for (const card of await this.cards()) { - await card.archive(); - } + this.cards().forEach(card => { + return card.archive(); + }); } - return await Lists.updateAsync(this._id, { $set: { archived: true, archivedAt: new Date() } }); + return { $set: { archived: true, archivedAt: new Date() } }; }, - async restore() { + restore() { if (this.isTemplateList()) { - for (const card of await this.allCards()) { - await card.restore(); - } + this.allCards().forEach(card => { + return card.restore(); + }); } - return await Lists.updateAsync(this._id, { $set: { archived: false } }); + return { $set: { archived: false } }; }, - async toggleSoftLimit(toggle) { - return await Lists.updateAsync(this._id, { $set: { 'wipLimit.soft': toggle } }); + toggleSoftLimit(toggle) { + return { $set: { 'wipLimit.soft': toggle } }; }, - async toggleWipLimit(toggle) { - return await Lists.updateAsync(this._id, { $set: { 'wipLimit.enabled': toggle } }); + toggleWipLimit(toggle) { + return { $set: { 'wipLimit.enabled': toggle } }; }, - async setWipLimit(limit) { - return await Lists.updateAsync(this._id, { $set: { 'wipLimit.value': limit } }); + setWipLimit(limit) { + return { $set: { 'wipLimit.value': limit } }; }, - async setColor(newColor) { - return await Lists.updateAsync(this._id, { $set: { color: newColor } }); + setColor(newColor) { + return { + $set: { + color: newColor, + }, + }; }, }); -Lists.userArchivedLists = async userId => { - return await ReactiveCache.getLists({ - boardId: { $in: await Boards.userBoardIds(userId, null) }, +Lists.userArchivedLists = userId => { + return ReactiveCache.getLists({ + boardId: { $in: Boards.userBoardIds(userId, null) }, archived: true, }) }; -Lists.userArchivedListIds = async () => { - const lists = await Lists.userArchivedLists(); - return lists.map(list => { return list._id; }); +Lists.userArchivedListIds = () => { + return Lists.userArchivedLists().map(list => { return list._id; }); }; -Lists.archivedLists = async () => { - return await ReactiveCache.getLists({ archived: true }); +Lists.archivedLists = () => { + return ReactiveCache.getLists({ archived: true }); }; -Lists.archivedListIds = async () => { - const lists = await Lists.archivedLists(); - return lists.map(list => { +Lists.archivedListIds = () => { + return Lists.archivedLists().map(list => { return list._id; }); }; Meteor.methods({ - async applyWipLimit(listId, limit) { + applyWipLimit(listId, limit) { check(listId, String); check(limit, Number); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const list = await ReactiveCache.getList(listId); - if (!list) { - throw new Meteor.Error('list-not-found', 'List not found'); - } - - const board = await ReactiveCache.getBoard(list.boardId); - if (!board || !board.hasAdmin(this.userId)) { - throw new Meteor.Error('not-authorized', 'You must be a board admin to modify WIP limits.'); - } - if (limit === 0) { limit = 1; } - await list.setWipLimit(limit); + ReactiveCache.getList(listId).setWipLimit(limit); }, - async enableWipLimit(listId) { + enableWipLimit(listId) { check(listId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); + const list = ReactiveCache.getList(listId); + if (list.getWipLimit('value') === 0) { + list.setWipLimit(1); } - - const list = await ReactiveCache.getList(listId); - if (!list) { - throw new Meteor.Error('list-not-found', 'List not found'); - } - - const board = await ReactiveCache.getBoard(list.boardId); - if (!board || !board.hasAdmin(this.userId)) { - throw new Meteor.Error('not-authorized', 'You must be a board admin to modify WIP limits.'); - } - - if ((await list.getWipLimit('value')) === 0) { - await list.setWipLimit(1); - } - await list.toggleWipLimit(!(await list.getWipLimit('enabled'))); + list.toggleWipLimit(!list.getWipLimit('enabled')); }, - async enableSoftLimit(listId) { + enableSoftLimit(listId) { check(listId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const list = await ReactiveCache.getList(listId); - if (!list) { - throw new Meteor.Error('list-not-found', 'List not found'); - } - - const board = await ReactiveCache.getBoard(list.boardId); - if (!board || !board.hasAdmin(this.userId)) { - throw new Meteor.Error('not-authorized', 'You must be a board admin to modify WIP limits.'); - } - - await list.toggleSoftLimit(!(await list.getWipLimit('soft'))); + const list = ReactiveCache.getList(listId); + list.toggleSoftLimit(!list.getWipLimit('soft')); }, - async myLists() { + myLists() { // my lists - const lists = await ReactiveCache.getLists( - { - boardId: { $in: await Boards.userBoardIds(this.userId) }, - archived: false, - }, - { - fields: { title: 1 }, - }, - ); - return _.uniq(lists.map(list => list.title)).sort(); - }, - - async updateListSort(listId, boardId, updateData) { - check(listId, String); - check(boardId, String); - check(updateData, Object); - - const board = await ReactiveCache.getBoard(boardId); - if (!board) { - throw new Meteor.Error('board-not-found', 'Board not found'); - } - - if (Meteor.isServer) { - if (typeof allowIsBoardMember === 'function') { - if (!allowIsBoardMember(this.userId, board)) { - throw new Meteor.Error('permission-denied', 'User does not have permission to modify this board'); - } - } - } - - const list = await ReactiveCache.getList(listId); - if (!list) { - throw new Meteor.Error('list-not-found', 'List not found'); - } - - const validUpdateFields = ['sort', 'swimlaneId', 'updatedAt', 'modifiedAt']; - Object.keys(updateData).forEach(field => { - if (!validUpdateFields.includes(field)) { - throw new Meteor.Error('invalid-field', `Field ${field} is not allowed`); - } - }); - - if (updateData.swimlaneId) { - const swimlane = await ReactiveCache.getSwimlane(updateData.swimlaneId); - if (!swimlane || swimlane.boardId !== boardId) { - throw new Meteor.Error('invalid-swimlane', 'Invalid swimlane for this board'); - } - } - - await Lists.updateAsync( - listId, - { - $set: { - ...updateData, - modifiedAt: new Date(), + return _.uniq( + ReactiveCache.getLists( + { + boardId: { $in: Boards.userBoardIds(this.userId) }, + archived: false, }, - }, - ); - - return { - success: true, - listId, - updatedFields: Object.keys(updateData), - timestamp: new Date().toISOString(), - }; + { + fields: { title: 1 }, + }, + ) + .map(list => { + return list.title; + }), + ).sort(); }, }); -if (Meteor.isServer) { - Meteor.startup(async () => { - await Lists._collection.rawCollection().createIndex({ modifiedAt: -1 }); - await Lists._collection.rawCollection().createIndex({ boardId: 1 }); - await Lists._collection.rawCollection().createIndex({ archivedAt: -1 }); - }); -} - -Lists.after.insert((userId, doc) => { - Activities.insert({ - userId, - type: 'list', - activityType: 'createList', - boardId: doc.boardId, - listId: doc._id, - // this preserves the name so that the activity can be useful after the - // list is deleted - title: doc.title, - }); - - // Track original position for new lists - Meteor.setTimeout(() => { - const list = Lists.findOne(doc._id); - if (list) { - list.trackOriginalPosition(); - } - }, 100); -}); - -Lists.before.remove(async (userId, doc) => { - const cards = await ReactiveCache.getCards({ listId: doc._id }); - if (cards) { - for (const card of cards) { - await Cards.removeAsync(card._id); - } - } - await Activities.insertAsync({ - userId, - type: 'list', - activityType: 'removeList', - boardId: doc.boardId, - listId: doc._id, - title: doc.title, - }); -}); - -// Ensure we don't fetch previous doc in after.update hook Lists.hookOptions.after.update = { fetchPrevious: false }; -Lists.after.update((userId, doc, fieldNames) => { - if (fieldNames.includes('title')) { - Activities.insert({ - userId, - type: 'list', - activityType: 'changedListTitle', - listId: doc._id, - boardId: doc.boardId, - // this preserves the name so that the activity can be useful after the - // list is deleted - title: doc.title, - }); - } else if (doc.archived) { - Activities.insert({ - userId, - type: 'list', - activityType: 'archivedList', - listId: doc._id, - boardId: doc.boardId, - // this preserves the name so that the activity can be useful after the - // list is deleted - title: doc.title, - }); - } else if (fieldNames.includes('archived')) { - Activities.insert({ - userId, - type: 'list', - activityType: 'restoredList', - listId: doc._id, - boardId: doc.boardId, - // this preserves the name so that the activity can be useful after the - // list is deleted - title: doc.title, - }); - } +if (Meteor.isServer) { + Meteor.startup(() => { + Lists._collection.createIndex({ modifiedAt: -1 }); + Lists._collection.createIndex({ boardId: 1 }); + Lists._collection.createIndex({ archivedAt: -1 }); + }); - // When sort or swimlaneId change, trigger a pub/sub refresh marker - if (fieldNames.includes('sort') || fieldNames.includes('swimlaneId')) { - Lists.direct.update( - { _id: doc._id }, - { $set: { _updatedAt: new Date() } }, - ); - } -}); + Lists.after.insert((userId, doc) => { + Activities.insert({ + userId, + type: 'list', + activityType: 'createList', + boardId: doc.boardId, + listId: doc._id, + // this preserves the name so that the activity can be useful after the + // list is deleted + title: doc.title, + }); + + // Track original position for new lists + Meteor.setTimeout(() => { + const list = Lists.findOne(doc._id); + if (list) { + list.trackOriginalPosition(); + } + }, 100); + }); + + Lists.before.remove((userId, doc) => { + const cards = ReactiveCache.getCards({ listId: doc._id }); + if (cards) { + cards.forEach(card => { + Cards.remove(card._id); + }); + } + Activities.insert({ + userId, + type: 'list', + activityType: 'removeList', + boardId: doc.boardId, + listId: doc._id, + title: doc.title, + }); + }); + + Lists.after.update((userId, doc, fieldNames) => { + if (fieldNames.includes('title')) { + Activities.insert({ + userId, + type: 'list', + activityType: 'changedListTitle', + listId: doc._id, + boardId: doc.boardId, + // this preserves the name so that the activity can be useful after the + // list is deleted + title: doc.title, + }); + } else if (doc.archived) { + Activities.insert({ + userId, + type: 'list', + activityType: 'archivedList', + listId: doc._id, + boardId: doc.boardId, + // this preserves the name so that the activity can be useful after the + // list is deleted + title: doc.title, + }); + } else if (fieldNames.includes('archived')) { + Activities.insert({ + userId, + type: 'list', + activityType: 'restoredList', + listId: doc._id, + boardId: doc.boardId, + // this preserves the name so that the activity can be useful after the + // list is deleted + title: doc.title, + }); + } + }); +} //LISTS REST API if (Meteor.isServer) { @@ -662,14 +529,14 @@ if (Meteor.isServer) { * @return_type [{_id: string, * title: string}] */ - JsonRoutes.add('GET', '/api/boards/:boardId/lists', async function(req, res) { + JsonRoutes.add('GET', '/api/boards/:boardId/lists', function(req, res) { try { const paramBoardId = req.params.boardId; Authentication.checkBoardAccess(req.userId, paramBoardId); JsonRoutes.sendResult(res, { code: 200, - data: (await ReactiveCache.getLists({ boardId: paramBoardId, archived: false })).map( + data: ReactiveCache.getLists({ boardId: paramBoardId, archived: false }).map( function(doc) { return { _id: doc._id, @@ -694,7 +561,7 @@ if (Meteor.isServer) { * @param {string} listId the List ID * @return_type Lists */ - JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId', async function( + JsonRoutes.add('GET', '/api/boards/:boardId/lists/:listId', function( req, res, ) { @@ -704,7 +571,7 @@ if (Meteor.isServer) { Authentication.checkBoardAccess(req.userId, paramBoardId); JsonRoutes.sendResult(res, { code: 200, - data: await ReactiveCache.getList({ + data: ReactiveCache.getList({ _id: paramListId, boardId: paramBoardId, archived: false, @@ -726,12 +593,12 @@ if (Meteor.isServer) { * @param {string} title the title of the List * @return_type {_id: string} */ - JsonRoutes.add('POST', '/api/boards/:boardId/lists', async function(req, res) { + JsonRoutes.add('POST', '/api/boards/:boardId/lists', function(req, res) { try { const paramBoardId = req.params.boardId; - Authentication.checkBoardWriteAccess(req.userId, paramBoardId); - const board = await ReactiveCache.getBoard(paramBoardId); - const id = await Lists.insertAsync({ + Authentication.checkBoardAccess(req.userId, paramBoardId); + const board = ReactiveCache.getBoard(paramBoardId); + const id = Lists.insert({ title: req.body.title, boardId: paramBoardId, sort: board.lists().length, @@ -767,7 +634,7 @@ if (Meteor.isServer) { * @param {boolean} [collapsed] whether the list is collapsed * @return_type {_id: string} */ - JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId', async function( + JsonRoutes.add('PUT', '/api/boards/:boardId/lists/:listId', function( req, res, ) { @@ -775,9 +642,9 @@ if (Meteor.isServer) { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; let updated = false; - Authentication.checkBoardWriteAccess(req.userId, paramBoardId); + Authentication.checkBoardAccess(req.userId, paramBoardId); - const list = await ReactiveCache.getList({ + const list = ReactiveCache.getList({ _id: paramListId, boardId: paramBoardId, archived: false, @@ -851,8 +718,23 @@ if (Meteor.isServer) { updated = true; } - // NOTE: collapsed state removed from board-level - // It's per-user only - use user profile methods instead + // Update collapsed status if provided + if (req.body.hasOwnProperty('collapsed')) { + const newCollapsed = req.body.collapsed; + Lists.direct.update( + { + _id: paramListId, + boardId: paramBoardId, + archived: false, + }, + { + $set: { + collapsed: newCollapsed, + }, + }, + ); + updated = true; + } // Update wipLimit if provided if (req.body.wipLimit) { @@ -915,7 +797,7 @@ if (Meteor.isServer) { try { const paramBoardId = req.params.boardId; const paramListId = req.params.listId; - Authentication.checkBoardWriteAccess(req.userId, paramBoardId); + Authentication.checkBoardAccess(req.userId, paramBoardId); Lists.remove({ _id: paramListId, boardId: paramBoardId }); JsonRoutes.sendResult(res, { code: 200, @@ -978,7 +860,7 @@ Lists.helpers({ hasMovedFromOriginalPosition() { const history = this.getOriginalPosition(); if (!history) return false; - + const currentSwimlaneId = this.swimlaneId || null; return history.originalPosition.sort !== this.sort || history.originalSwimlaneId !== currentSwimlaneId; @@ -990,9 +872,9 @@ Lists.helpers({ getOriginalPositionDescription() { const history = this.getOriginalPosition(); if (!history) return 'No original position data'; - - const swimlaneInfo = history.originalSwimlaneId ? - ` in swimlane ${history.originalSwimlaneId}` : + + const swimlaneInfo = history.originalSwimlaneId ? + ` in swimlane ${history.originalSwimlaneId}` : ' in default swimlane'; return `Original position: ${history.originalPosition.sort || 0}${swimlaneInfo}`; }, diff --git a/models/lockoutSettings.js b/models/lockoutSettings.js index 454b64481..04bd18cc6 100644 --- a/models/lockoutSettings.js +++ b/models/lockoutSettings.js @@ -49,17 +49,17 @@ LockoutSettings.attachSchema( LockoutSettings.allow({ update(userId) { - const user = Meteor.users.findOne(userId); + const user = ReactiveCache.getUser(userId); return user && user.isAdmin; }, }); if (Meteor.isServer) { - Meteor.startup(async () => { - await LockoutSettings._collection.createIndexAsync({ modifiedAt: -1 }); + Meteor.startup(() => { + LockoutSettings._collection.createIndex({ modifiedAt: -1 }); // Known users settings - await LockoutSettings.upsertAsync( + LockoutSettings.upsert( { _id: 'known-failuresBeforeLockout' }, { $setOnInsert: { @@ -71,7 +71,7 @@ if (Meteor.isServer) { }, ); - await LockoutSettings.upsertAsync( + LockoutSettings.upsert( { _id: 'known-lockoutPeriod' }, { $setOnInsert: { @@ -83,7 +83,7 @@ if (Meteor.isServer) { }, ); - await LockoutSettings.upsertAsync( + LockoutSettings.upsert( { _id: 'known-failureWindow' }, { $setOnInsert: { @@ -99,7 +99,7 @@ if (Meteor.isServer) { const typoVar = process.env.ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BERORE; const correctVar = process.env.ACCOUNTS_LOCKOUT_UNKNOWN_USERS_FAILURES_BEFORE; - await LockoutSettings.upsertAsync( + LockoutSettings.upsert( { _id: 'unknown-failuresBeforeLockout' }, { $setOnInsert: { @@ -111,7 +111,7 @@ if (Meteor.isServer) { }, ); - await LockoutSettings.upsertAsync( + LockoutSettings.upsert( { _id: 'unknown-lockoutPeriod' }, { $setOnInsert: { @@ -123,7 +123,7 @@ if (Meteor.isServer) { }, ); - await LockoutSettings.upsertAsync( + LockoutSettings.upsert( { _id: 'unknown-failureWindow' }, { $setOnInsert: { @@ -139,33 +139,17 @@ if (Meteor.isServer) { LockoutSettings.helpers({ getKnownConfig() { - // Fetch all settings in one query instead of 3 separate queries - const settings = LockoutSettings.find({ - _id: { $in: ['known-failuresBeforeLockout', 'known-lockoutPeriod', 'known-failureWindow'] } - }, { fields: { _id: 1, value: 1 } }).fetch(); - - const settingsMap = {}; - settings.forEach(s => { settingsMap[s._id] = s.value; }); - return { - failuresBeforeLockout: settingsMap['known-failuresBeforeLockout'] || 3, - lockoutPeriod: settingsMap['known-lockoutPeriod'] || 60, - failureWindow: settingsMap['known-failureWindow'] || 15 + failuresBeforeLockout: LockoutSettings.findOne('known-failuresBeforeLockout')?.value || 3, + lockoutPeriod: LockoutSettings.findOne('known-lockoutPeriod')?.value || 60, + failureWindow: LockoutSettings.findOne('known-failureWindow')?.value || 15 }; }, getUnknownConfig() { - // Fetch all settings in one query instead of 3 separate queries - const settings = LockoutSettings.find({ - _id: { $in: ['unknown-failuresBeforeLockout', 'unknown-lockoutPeriod', 'unknown-failureWindow'] } - }, { fields: { _id: 1, value: 1 } }).fetch(); - - const settingsMap = {}; - settings.forEach(s => { settingsMap[s._id] = s.value; }); - return { - failuresBeforeLockout: settingsMap['unknown-failuresBeforeLockout'] || 3, - lockoutPeriod: settingsMap['unknown-lockoutPeriod'] || 60, - failureWindow: settingsMap['unknown-failureWindow'] || 15 + failuresBeforeLockout: LockoutSettings.findOne('unknown-failuresBeforeLockout')?.value || 3, + lockoutPeriod: LockoutSettings.findOne('unknown-lockoutPeriod')?.value || 60, + failureWindow: LockoutSettings.findOne('unknown-failureWindow')?.value || 15 }; } }); diff --git a/models/org.js b/models/org.js index eeb5cb6c3..17c0b1981 100644 --- a/models/org.js +++ b/models/org.js @@ -88,7 +88,7 @@ Org.attachSchema( if (Meteor.isServer) { Org.allow({ insert(userId, doc) { - const user = Meteor.users.findOne(userId); + const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser(); if (user?.isAdmin) return true; if (!user) { @@ -97,7 +97,7 @@ if (Meteor.isServer) { return doc._id === userId; }, update(userId, doc) { - const user = Meteor.users.findOne(userId); + const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser(); if (user?.isAdmin) return true; if (!user) { @@ -106,7 +106,7 @@ if (Meteor.isServer) { return doc._id === userId; }, remove(userId, doc) { - const user = Meteor.users.findOne(userId); + const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser(); if (user?.isAdmin) return true; if (!user) { @@ -119,7 +119,7 @@ if (Meteor.isServer) { Meteor.methods({ - async setCreateOrg( + setCreateOrg( orgDisplayName, orgDesc, orgShortName, @@ -127,7 +127,7 @@ if (Meteor.isServer) { orgWebsite, orgIsActive, ) { - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { check(orgDisplayName, String); check(orgDesc, String); check(orgShortName, String); @@ -135,7 +135,7 @@ if (Meteor.isServer) { check(orgWebsite, String); check(orgIsActive, Boolean); - const nOrgNames = (await ReactiveCache.getOrgs({ orgShortName })).length; + const nOrgNames = ReactiveCache.getOrgs({ orgShortName }).length; if (nOrgNames > 0) { throw new Meteor.Error('orgname-already-taken'); } else { @@ -150,7 +150,7 @@ if (Meteor.isServer) { } } }, - async setCreateOrgFromOidc( + setCreateOrgFromOidc( orgDisplayName, orgDesc, orgShortName, @@ -165,7 +165,7 @@ if (Meteor.isServer) { check(orgWebsite, String); check(orgIsActive, Boolean); - const nOrgNames = (await ReactiveCache.getOrgs({ orgShortName })).length; + const nOrgNames = ReactiveCache.getOrgs({ orgShortName }).length; if (nOrgNames > 0) { throw new Meteor.Error('orgname-already-taken'); } else { @@ -179,19 +179,19 @@ if (Meteor.isServer) { }); } }, - async setOrgDisplayName(org, orgDisplayName) { - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + setOrgDisplayName(org, orgDisplayName) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { check(org, Object); check(orgDisplayName, String); Org.update(org, { $set: { orgDisplayName: orgDisplayName }, }); - await Meteor.callAsync('setUsersOrgsOrgDisplayName', org._id, orgDisplayName); + Meteor.call('setUsersOrgsOrgDisplayName', org._id, orgDisplayName); } }, - async setOrgDesc(org, orgDesc) { - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + setOrgDesc(org, orgDesc) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { check(org, Object); check(orgDesc, String); Org.update(org, { @@ -200,8 +200,8 @@ if (Meteor.isServer) { } }, - async setOrgShortName(org, orgShortName) { - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + setOrgShortName(org, orgShortName) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { check(org, Object); check(orgShortName, String); Org.update(org, { @@ -210,8 +210,8 @@ if (Meteor.isServer) { } }, - async setAutoAddUsersWithDomainName(org, orgAutoAddUsersWithDomainName) { - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + setAutoAddUsersWithDomainName(org, orgAutoAddUsersWithDomainName) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { check(org, Object); check(orgAutoAddUsersWithDomainName, String); Org.update(org, { @@ -220,8 +220,8 @@ if (Meteor.isServer) { } }, - async setOrgIsActive(org, orgIsActive) { - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + setOrgIsActive(org, orgIsActive) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { check(org, Object); check(orgIsActive, Boolean); Org.update(org, { @@ -229,7 +229,7 @@ if (Meteor.isServer) { }); } }, - async setOrgAllFieldsFromOidc( + setOrgAllFieldsFromOidc( org, orgDisplayName, orgDesc, @@ -255,9 +255,9 @@ if (Meteor.isServer) { orgIsActive: orgIsActive, }, }); - await Meteor.callAsync('setUsersOrgsOrgDisplayName', org._id, orgDisplayName); + Meteor.call('setUsersOrgsOrgDisplayName', org._id, orgDisplayName); }, - async setOrgAllFields( + setOrgAllFields( org, orgDisplayName, orgDesc, @@ -266,7 +266,7 @@ if (Meteor.isServer) { orgWebsite, orgIsActive, ) { - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { check(org, Object); check(orgDisplayName, String); check(orgDesc, String); @@ -284,7 +284,7 @@ if (Meteor.isServer) { orgIsActive: orgIsActive, }, }); - await Meteor.callAsync('setUsersOrgsOrgDisplayName', org._id, orgDisplayName); + Meteor.call('setUsersOrgsOrgDisplayName', org._id, orgDisplayName); } }, }); @@ -292,9 +292,9 @@ if (Meteor.isServer) { if (Meteor.isServer) { // Index for Organization name. - Meteor.startup(async () => { - // Org._collection.createIndexAsync({ name: -1 }); - await Org._collection.createIndexAsync({ orgDisplayName: 1 }); + Meteor.startup(() => { + // Org._collection.createIndex({ name: -1 }); + Org._collection.createIndex({ orgDisplayName: 1 }); }); } diff --git a/models/orgUser.js b/models/orgUser.js index 8eecc86fc..026cbdbcf 100644 --- a/models/orgUser.js +++ b/models/orgUser.js @@ -1,5 +1,3 @@ -import { incrementCounter } from './counters'; - OrgUser = new Mongo.Collection('orgUser'); /** @@ -16,7 +14,7 @@ OrgUser.attachSchema( // eslint-disable-next-line consistent-return autoValue() { if (this.isInsert && !this.isSet) { - return incrementCounter('orgUserId', 1); + return incrementCounter('counters', 'orgUserId', 1); } }, }, @@ -75,9 +73,9 @@ OrgUser.attachSchema( if (Meteor.isServer) { // Index for Organization User. - Meteor.startup(async () => { - await OrgUser._collection.createIndexAsync({ orgId: -1 }); - await OrgUser._collection.createIndexAsync({ orgId: -1, userId: -1 }); + Meteor.startup(() => { + OrgUser._collection.createIndex({ orgId: -1 }); + OrgUser._collection.createIndex({ orgId: -1, userId: -1 }); }); } diff --git a/models/positionHistory.js b/models/positionHistory.js index c741dcdd4..a70b9fbab 100644 --- a/models/positionHistory.js +++ b/models/positionHistory.js @@ -160,10 +160,10 @@ PositionHistory.helpers({ }); if (Meteor.isServer) { - Meteor.startup(async () => { - await PositionHistory._collection.createIndexAsync({ boardId: 1, entityType: 1, entityId: 1 }); - await PositionHistory._collection.createIndexAsync({ boardId: 1, entityType: 1 }); - await PositionHistory._collection.createIndexAsync({ createdAt: -1 }); + Meteor.startup(() => { + PositionHistory._collection.createIndex({ boardId: 1, entityType: 1, entityId: 1 }); + PositionHistory._collection.createIndex({ boardId: 1, entityType: 1 }); + PositionHistory._collection.createIndex({ createdAt: -1 }); }); } diff --git a/models/rules.js b/models/rules.js index 5d533372b..38b3c87a5 100644 --- a/models/rules.js +++ b/models/rules.js @@ -50,10 +50,13 @@ Rules.attachSchema( }), ); -Rules.helpers({ - async rename(description) { - return await Rules.updateAsync(this._id, { $set: { description } }); +Rules.mutations({ + rename(description) { + return { $set: { description } }; }, +}); + +Rules.helpers({ getAction() { return ReactiveCache.getAction(this.actionId); }, @@ -73,19 +76,19 @@ Rules.helpers({ Rules.allow({ insert(userId, doc) { - return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId)); }, update(userId, doc) { - return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId)); }, remove(userId, doc) { - return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId)); }, }); if (Meteor.isServer) { - Meteor.startup(async () => { - await Rules._collection.createIndexAsync({ modifiedAt: -1 }); + Meteor.startup(() => { + Rules._collection.createIndex({ modifiedAt: -1 }); }); } diff --git a/models/server/ExporterCardPDF.js b/models/server/ExporterCardPDF.js index fada5d593..37551e6cb 100644 --- a/models/server/ExporterCardPDF.js +++ b/models/server/ExporterCardPDF.js @@ -1,332 +1,647 @@ import { ReactiveCache } from '/imports/reactiveCache'; - -const PAGE_WIDTH = 595; -const PAGE_HEIGHT = 842; -const PAGE_MARGIN = 50; -const LINE_HEIGHT = 14; -const FONT_SIZE = 10; -const TEXT_WIDTH = 90; - -function sanitizeFilename(value) { - return String(value || 'export-card') - .replace(/[^a-z0-9._-]+/gi, '-') - .replace(/-+/g, '-') - .replace(/^-|-$/g, '') - .slice(0, 80) || 'export-card'; -} - -function normalizePdfText(value) { - return String(value ?? '') - .replace(/<[^>]*>/g, ' ') - .replace(/\r/g, '') - .replace(/\t/g, ' ') - .replace(/[^\x20-\x7E\n]/g, '?'); -} - -function escapePdfText(value) { - return normalizePdfText(value) - .replace(/\\/g, '\\\\') - .replace(/\(/g, '\\(') - .replace(/\)/g, '\\)'); -} - -function wrapLine(line, width = TEXT_WIDTH) { - if (!line) { - return ['']; - } - - const words = line.split(/\s+/).filter(Boolean); - if (words.length === 0) { - return ['']; - } - - const wrapped = []; - let current = ''; - - for (const word of words) { - if (!current) { - current = word; - continue; - } - - if (`${current} ${word}`.length <= width) { - current = `${current} ${word}`; - continue; - } - - wrapped.push(current); - current = word; - } - - if (current) { - wrapped.push(current); - } - - const splitLongWords = []; - for (const item of wrapped) { - if (item.length <= width) { - splitLongWords.push(item); - continue; - } - - for (let index = 0; index < item.length; index += width) { - splitLongWords.push(item.slice(index, index + width)); - } - } - - return splitLongWords; -} - -function wrapTextBlock(text) { - return normalizePdfText(text) - .split('\n') - .flatMap(line => wrapLine(line)); -} - -function paginateLines(lines) { - const linesPerPage = Math.floor( - (PAGE_HEIGHT - PAGE_MARGIN * 2) / LINE_HEIGHT, - ); - const pages = []; - - for (let index = 0; index < lines.length; index += linesPerPage) { - pages.push(lines.slice(index, index + linesPerPage)); - } - - return pages.length > 0 ? pages : [['No data']]; -} - -function buildPdfBuffer(lines) { - const pages = paginateLines(lines); - const objects = []; - const addObject = content => { - objects.push(content); - return objects.length; - }; - - const catalogId = addObject(''); - const pagesId = addObject(''); - const fontId = addObject( - '<< /Type /Font /Subtype /Type1 /BaseFont /Courier >>', - ); - - const pageIds = []; - - for (const pageLines of pages) { - const textCommands = ['BT', `/F1 ${FONT_SIZE} Tf`, `${LINE_HEIGHT} TL`]; - textCommands.push( - `1 0 0 1 ${PAGE_MARGIN} ${PAGE_HEIGHT - PAGE_MARGIN - FONT_SIZE} Tm`, - ); - - pageLines.forEach((line, index) => { - if (index > 0) { - textCommands.push('T*'); - } - textCommands.push(`(${escapePdfText(line)}) Tj`); - }); - - textCommands.push('ET'); - const stream = textCommands.join('\n'); - const contentId = addObject( - `<< /Length ${Buffer.byteLength(stream, 'utf8')} >>\nstream\n${stream}\nendstream`, - ); - const pageId = addObject( - `<< /Type /Page /Parent ${pagesId} 0 R /MediaBox [0 0 ${PAGE_WIDTH} ${PAGE_HEIGHT}] /Resources << /Font << /F1 ${fontId} 0 R >> >> /Contents ${contentId} 0 R >>`, - ); - pageIds.push(pageId); - } - - objects[catalogId - 1] = `<< /Type /Catalog /Pages ${pagesId} 0 R >>`; - objects[pagesId - 1] = `<< /Type /Pages /Kids [${pageIds.map(id => `${id} 0 R`).join(' ')}] /Count ${pageIds.length} >>`; - - let pdf = '%PDF-1.4\n'; - const offsets = [0]; - - objects.forEach((object, index) => { - offsets.push(Buffer.byteLength(pdf, 'utf8')); - pdf += `${index + 1} 0 obj\n${object}\nendobj\n`; - }); - - const xrefOffset = Buffer.byteLength(pdf, 'utf8'); - pdf += `xref\n0 ${objects.length + 1}\n`; - pdf += '0000000000 65535 f \n'; - - for (let index = 1; index < offsets.length; index += 1) { - pdf += `${String(offsets[index]).padStart(10, '0')} 00000 n \n`; - } - - pdf += `trailer\n<< /Size ${objects.length + 1} /Root ${catalogId} 0 R >>\nstartxref\n${xrefOffset}\n%%EOF`; - return Buffer.from(pdf, 'utf8'); -} - -function formatDateValue(value) { - if (!(value instanceof Date) || Number.isNaN(value.getTime())) { - return '-'; - } - - return value.toISOString().replace('T', ' ').slice(0, 16); -} - -function formatUser(user) { - if (!user) { - return 'Unknown'; - } - - return user.profile?.fullname || user.username || user.profile?.initials || user._id; -} +// exporter maybe is broken since Gridfs introduced, add fs and path +import { createWorkbook } from './createWorkbook'; +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar +} from '/imports/lib/dateUtils'; class ExporterCardPDF { - constructor(boardId, listId, cardId) { + constructor(boardId) { this._boardId = boardId; - this._listId = listId; - this._cardId = cardId; } - async _getCardData() { - const board = await ReactiveCache.getBoard(this._boardId); - const list = await ReactiveCache.getList({ - _id: this._listId, - boardId: this._boardId, - }); - const card = await ReactiveCache.getCard({ - _id: this._cardId, - boardId: this._boardId, - listId: this._listId, - }); + build(res) { - if (!board || !list || !card) { - return null; - } + /* + const fs = Npm.require('fs'); + const os = Npm.require('os'); + const path = Npm.require('path'); - const swimlane = card.swimlaneId - ? await ReactiveCache.getSwimlane({ _id: card.swimlaneId }) - : null; - const checklists = await ReactiveCache.getChecklists( - { cardId: this._cardId }, - { sort: { sort: 1 } }, - ); - const comments = await ReactiveCache.getCardComments( - { cardId: this._cardId }, - { sort: { createdAt: 1 } }, - ); - - const checklistItemsByChecklistId = {}; - for (const checklist of checklists) { - checklistItemsByChecklistId[checklist._id] = await ReactiveCache.getChecklistItems( - { checklistId: checklist._id }, - { sort: { sort: 1 } }, - ); - } - - const userIds = new Set([ - card.userId, - ...(card.members || []), - ...(card.assignees || []), - ...comments.map(comment => comment.userId), - ]); - const usersById = {}; - - await Promise.all( - [...userIds] - .filter(Boolean) - .map(async userId => { - usersById[userId] = await ReactiveCache.getUser({ _id: userId }); - }), - ); - - return { - board, - list, - card, - swimlane, - checklists, - checklistItemsByChecklistId, - comments, - usersById, - }; - } - - async build(res) { - const data = await this._getCardData(); - if (!data) { - res.writeHead(404, { 'Content-Type': 'text/plain; charset=utf-8' }); - res.end('Card not found'); - return; - } - - const { board, list, card, swimlane, checklists, checklistItemsByChecklistId, comments, usersById } = data; - const labelsById = Object.fromEntries( - (board.labels || []) - .filter(label => label && label._id) - .map(label => [label._id, label.name || label.color || label._id]), - ); - - const lines = [ - 'Wekan Card Export', - '', - `Title: ${card.title || '-'}`, - `Board: ${board.title || '-'}`, - `List: ${list.title || '-'}`, - `Swimlane: ${swimlane?.title || '-'}`, - `Created by: ${formatUser(usersById[card.userId])}`, - `Members: ${(card.members || []).map(userId => formatUser(usersById[userId])).join(', ') || '-'}`, - `Assignees: ${(card.assignees || []).map(userId => formatUser(usersById[userId])).join(', ') || '-'}`, - `Labels: ${(card.labelIds || []).map(labelId => labelsById[labelId] || labelId).join(', ') || '-'}`, - `Created: ${formatDateValue(card.createdAt)}`, - `Last activity: ${formatDateValue(card.dateLastActivity)}`, - `Received: ${formatDateValue(card.receivedAt)}`, - `Start: ${formatDateValue(card.startAt)}`, - `Due: ${formatDateValue(card.dueAt)}`, - `End: ${formatDateValue(card.endAt)}`, - `Spent time: ${card.spentTime ?? '-'}`, - '', - 'Description:', - ...wrapTextBlock(card.description || '-'), - ]; - - lines.push('', 'Checklists:'); - if (checklists.length === 0) { - lines.push('-'); - } else { - for (const checklist of checklists) { - lines.push(...wrapTextBlock(`- ${checklist.title || 'Checklist'}`)); - const items = checklistItemsByChecklistId[checklist._id] || []; - if (items.length === 0) { - lines.push(' (no items)'); - continue; - } - for (const item of items) { - lines.push(...wrapTextBlock(` ${item.isFinished ? '[x]' : '[ ]'} ${item.title || ''}`)); - } - } - } - - lines.push('', 'Comments:'); - if (comments.length === 0) { - lines.push('-'); - } else { - for (const comment of comments) { - lines.push( - ...wrapTextBlock( - `- ${formatDateValue(comment.createdAt)} ${formatUser(usersById[comment.userId])}: ${comment.text || ''}`, - ), + const byBoard = { + boardId: this._boardId, + }; + const byBoardNoLinked = { + boardId: this._boardId, + linkedId: { + $in: ['', null], + }, + }; + // we do not want to retrieve boardId in related elements + const noBoardId = { + fields: { + boardId: 0, + }, + }; + const result = { + _format: 'wekan-board-1.0.0', + }; + _.extend( + result, + ReactiveCache.getBoard(this._boardId, { + fields: { + stars: 0, + }, + }), ); - } - } + result.lists = ReactiveCache.getLists(byBoard, noBoardId); + result.cards = ReactiveCache.getCards(byBoardNoLinked, noBoardId); + result.swimlanes = ReactiveCache.getSwimlanes(byBoard, noBoardId); + result.customFields = ReactiveCache.getCustomFields( + { + boardIds: { + $in: [this.boardId], + }, + }, + { + fields: { + boardId: 0, + }, + }, + ); + result.comments = ReactiveCache.getCardComments(byBoard, noBoardId); + result.activities = ReactiveCache.getActivities(byBoard, noBoardId); + result.rules = ReactiveCache.getRules(byBoard, noBoardId); + result.checklists = []; + result.checklistItems = []; + result.subtaskItems = []; + result.triggers = []; + result.actions = []; + result.cards.forEach((card) => { + result.checklists.push( + ...ReactiveCache.getChecklists({ + cardId: card._id, + }), + ); + result.checklistItems.push( + ...ReactiveCache.getChecklistItems({ + cardId: card._id, + }), + ); + result.subtaskItems.push( + ...ReactiveCache.getCards({ + parentId: card._id, + }), + ); + }); + result.rules.forEach((rule) => { + result.triggers.push( + ...ReactiveCache.getTriggers( + { + _id: rule.triggerId, + }, + noBoardId, + ), + ); + result.actions.push( + ...ReactiveCache.getActions( + { + _id: rule.actionId, + }, + noBoardId, + ), + ); + }); - const filename = `${sanitizeFilename(card.title)}.pdf`; - const pdf = buildPdfBuffer(lines); + // we also have to export some user data - as the other elements only + // include id but we have to be careful: + // 1- only exports users that are linked somehow to that board + // 2- do not export any sensitive information + const users = {}; + result.members.forEach((member) => { + users[member.userId] = true; + }); + result.lists.forEach((list) => { + users[list.userId] = true; + }); + result.cards.forEach((card) => { + users[card.userId] = true; + if (card.members) { + card.members.forEach((memberId) => { + users[memberId] = true; + }); + } + if (card.assignees) { + card.assignees.forEach((memberId) => { + users[memberId] = true; + }); + } + }); + result.comments.forEach((comment) => { + users[comment.userId] = true; + }); + result.activities.forEach((activity) => { + users[activity.userId] = true; + }); + result.checklists.forEach((checklist) => { + users[checklist.userId] = true; + }); + const byUserIds = { + _id: { + $in: Object.getOwnPropertyNames(users), + }, + }; + // we use whitelist to be sure we do not expose inadvertently + // some secret fields that gets added to User later. + const userFields = { + fields: { + _id: 1, + username: 1, + 'profile.initials': 1, + 'profile.avatarUrl': 1, + }, + }; + result.users = ReactiveCache.getUsers(byUserIds, userFields) + .map((user) => { + // user avatar is stored as a relative url, we export absolute + if ((user.profile || {}).avatarUrl) { + user.profile.avatarUrl = FlowRouter.url(user.profile.avatarUrl); + } + return user; + }); - res.writeHead(200, { - 'Content-Type': 'application/pdf', - 'Content-Disposition': `attachment; filename="${filename}"`, - 'Content-Length': pdf.length, + //init exceljs workbook + const workbook = createWorkbook(); + workbook.creator = TAPi18n.__('export-board'); + workbook.lastModifiedBy = TAPi18n.__('export-board'); + workbook.created = new Date(); + workbook.modified = new Date(); + workbook.lastPrinted = new Date(); + const filename = `${result.title}.xlsx`; + //init worksheet + const worksheet = workbook.addWorksheet(result.title, { + properties: { + tabColor: { + argb: 'FFC0000', + }, + }, + pageSetup: { + paperSize: 9, + orientation: 'landscape', + }, + }); + //get worksheet + const ws = workbook.getWorksheet(result.title); + ws.properties.defaultRowHeight = 20; + //init columns + //Excel font. Western: Arial. zh-CN: 宋体 + ws.columns = [ + { + key: 'a', + width: 14, + }, + { + key: 'b', + width: 40, + }, + { + key: 'c', + width: 60, + }, + { + key: 'd', + width: 40, + }, + { + key: 'e', + width: 20, + }, + { + key: 'f', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'g', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'h', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'i', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'j', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'k', + width: 20, + style: { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }, + }, + { + key: 'l', + width: 20, + }, + { + key: 'm', + width: 20, + }, + { + key: 'n', + width: 20, + }, + { + key: 'o', + width: 20, + }, + { + key: 'p', + width: 20, + }, + { + key: 'q', + width: 20, + }, + { + key: 'r', + width: 20, + }, + ]; + + //add title line + ws.mergeCells('A1:H1'); + ws.getCell('A1').value = result.title; + ws.getCell('A1').style = { + font: { + name: TAPi18n.__('excel-font'), + size: '20', + }, + }; + ws.getCell('A1').alignment = { + vertical: 'middle', + horizontal: 'center', + }; + ws.getRow(1).height = 40; + //get member and assignee info + let jmem = ''; + let jassig = ''; + const jmeml = {}; + const jassigl = {}; + for (const i in result.users) { + jmem = `${jmem + result.users[i].username},`; + jmeml[result.users[i]._id] = result.users[i].username; + } + jmem = jmem.substr(0, jmem.length - 1); + for (const ia in result.users) { + jassig = `${jassig + result.users[ia].username},`; + jassigl[result.users[ia]._id] = result.users[ia].username; + } + jassig = jassig.substr(0, jassig.length - 1); + //get kanban list info + const jlist = {}; + for (const klist in result.lists) { + jlist[result.lists[klist]._id] = result.lists[klist].title; + } + //get kanban swimlanes info + const jswimlane = {}; + for (const kswimlane in result.swimlanes) { + jswimlane[result.swimlanes[kswimlane]._id] = + result.swimlanes[kswimlane].title; + } + //get kanban label info + const jlabel = {}; + var isFirst = 1; + for (const klabel in result.labels) { + // console.log(klabel); + if (isFirst == 0) { + jlabel[result.labels[klabel]._id] = `,${result.labels[klabel].name}`; + } else { + isFirst = 0; + jlabel[result.labels[klabel]._id] = result.labels[klabel].name; + } + } + //add data +8 hours + function addTZhours(jdate) { + const curdate = new Date(jdate); + const checkCorrectDate = new Date(curdate); + if (isValidDate(checkCorrectDate)) { + return curdate; + } else { + return ' '; + } + ////Do not add 8 hours to GMT. Use GMT instead. + ////Could not yet figure out how to get localtime. + //return new Date(curdate.setHours(curdate.getHours() + 8)); + //return curdate; + } + //add blank row + ws.addRow().values = ['', '', '', '', '', '']; + //add kanban info + ws.addRow().values = [ + TAPi18n.__('createdAt'), + addTZhours(result.createdAt), + TAPi18n.__('modifiedAt'), + addTZhours(result.modifiedAt), + TAPi18n.__('members'), + jmem, + ]; + ws.getRow(3).font = { + name: TAPi18n.__('excel-font'), + size: 10, + bold: true, + }; + ws.mergeCells('F3:R3'); + ws.getCell('B3').style = { + font: { + name: TAPi18n.__('excel-font'), + size: '10', + bold: true, + }, + numFmt: 'yyyy/mm/dd hh:mm:ss', + }; + //cell center + function cellCenter(cellno) { + ws.getCell(cellno).alignment = { + vertical: 'middle', + horizontal: 'center', + wrapText: true, + }; + } + function cellLeft(cellno) { + ws.getCell(cellno).alignment = { + vertical: 'middle', + horizontal: 'left', + wrapText: true, + }; + } + cellCenter('A3'); + cellCenter('B3'); + cellCenter('C3'); + cellCenter('D3'); + cellCenter('E3'); + cellLeft('F3'); + ws.getRow(3).height = 20; + //all border + function allBorder(cellno) { + ws.getCell(cellno).border = { + top: { + style: 'thin', + }, + left: { + style: 'thin', + }, + bottom: { + style: 'thin', + }, + right: { + style: 'thin', + }, + }; + } + allBorder('A3'); + allBorder('B3'); + allBorder('C3'); + allBorder('D3'); + allBorder('E3'); + allBorder('F3'); + //add blank row + ws.addRow().values = [ + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + '', + ]; + //add card title + //ws.addRow().values = ['编号', '标题', '创建人', '创建时间', '更新时间', '列表', '成员', '描述', '标签']; + //this is where order in which the excel file generates + ws.addRow().values = [ + TAPi18n.__('number'), + TAPi18n.__('title'), + TAPi18n.__('description'), + TAPi18n.__('parent-card'), + TAPi18n.__('owner'), + TAPi18n.__('createdAt'), + TAPi18n.__('last-modified-at'), + TAPi18n.__('card-received'), + TAPi18n.__('card-start'), + TAPi18n.__('card-due'), + TAPi18n.__('card-end'), + TAPi18n.__('list'), + TAPi18n.__('swimlane'), + TAPi18n.__('assignee'), + TAPi18n.__('members'), + TAPi18n.__('labels'), + TAPi18n.__('overtime-hours'), + TAPi18n.__('spent-time-hours'), + ]; + ws.getRow(5).height = 20; + allBorder('A5'); + allBorder('B5'); + allBorder('C5'); + allBorder('D5'); + allBorder('E5'); + allBorder('F5'); + allBorder('G5'); + allBorder('H5'); + allBorder('I5'); + allBorder('J5'); + allBorder('K5'); + allBorder('L5'); + allBorder('M5'); + allBorder('N5'); + allBorder('O5'); + allBorder('P5'); + allBorder('Q5'); + allBorder('R5'); + cellCenter('A5'); + cellCenter('B5'); + cellCenter('C5'); + cellCenter('D5'); + cellCenter('E5'); + cellCenter('F5'); + cellCenter('G5'); + cellCenter('H5'); + cellCenter('I5'); + cellCenter('J5'); + cellCenter('K5'); + cellCenter('L5'); + cellCenter('M5'); + cellCenter('N5'); + cellCenter('O5'); + cellCenter('P5'); + cellCenter('Q5'); + cellCenter('R5'); + ws.getRow(5).font = { + name: TAPi18n.__('excel-font'), + size: 12, + bold: true, + }; + //add blank row + //add card info + for (const i in result.cards) { + const jcard = result.cards[i]; + //get member info + let jcmem = ''; + for (const j in jcard.members) { + jcmem += jmeml[jcard.members[j]]; + jcmem += ' '; + } + //get assignee info + let jcassig = ''; + for (const ja in jcard.assignees) { + jcassig += jassigl[jcard.assignees[ja]]; + jcassig += ' '; + } + //get card label info + let jclabel = ''; + for (const jl in jcard.labelIds) { + jclabel += jlabel[jcard.labelIds[jl]]; + jclabel += ' '; + } + //get parent name + if (jcard.parentId) { + const parentCard = result.cards.find( + (card) => card._id === jcard.parentId, + ); + jcard.parentCardTitle = parentCard ? parentCard.title : ''; + } + + //add card detail + const t = Number(i) + 1; + ws.addRow().values = [ + t.toString(), + jcard.title, + jcard.description, + jcard.parentCardTitle, + jmeml[jcard.userId], + addTZhours(jcard.createdAt), + addTZhours(jcard.dateLastActivity), + addTZhours(jcard.receivedAt), + addTZhours(jcard.startAt), + addTZhours(jcard.dueAt), + addTZhours(jcard.endAt), + jlist[jcard.listId], + jswimlane[jcard.swimlaneId], + jcassig, + jcmem, + jclabel, + jcard.isOvertime ? 'true' : 'false', + jcard.spentTime, + ]; + const y = Number(i) + 6; + //ws.getRow(y).height = 25; + allBorder(`A${y}`); + allBorder(`B${y}`); + allBorder(`C${y}`); + allBorder(`D${y}`); + allBorder(`E${y}`); + allBorder(`F${y}`); + allBorder(`G${y}`); + allBorder(`H${y}`); + allBorder(`I${y}`); + allBorder(`J${y}`); + allBorder(`K${y}`); + allBorder(`L${y}`); + allBorder(`M${y}`); + allBorder(`N${y}`); + allBorder(`O${y}`); + allBorder(`P${y}`); + allBorder(`Q${y}`); + allBorder(`R${y}`); + cellCenter(`A${y}`); + ws.getCell(`B${y}`).alignment = { + wrapText: true, + }; + ws.getCell(`C${y}`).alignment = { + wrapText: true, + }; + ws.getCell(`M${y}`).alignment = { + wrapText: true, + }; + ws.getCell(`N${y}`).alignment = { + wrapText: true, + }; + ws.getCell(`O${y}`).alignment = { + wrapText: true, + }; + } + workbook.xlsx.write(res).then(function () {}); + */ + + var doc = new PDFDocument({size: 'A4', margin: 50}); + doc.fontSize(12); + doc.text('Some test text', 10, 30, {align: 'center', width: 200}); + this.response.writeHead(200, { + 'Content-type': 'application/pdf', + 'Content-Disposition': "attachment; filename=test.pdf" }); - res.end(pdf); + this.response.end( doc.outputSync() ); + } - async canExport(user) { - const board = await ReactiveCache.getBoard(this._boardId); + canExport(user) { + const board = ReactiveCache.getBoard(this._boardId); return board && board.isVisibleBy(user); } } diff --git a/models/server/ExporterExcel.js b/models/server/ExporterExcel.js index 55d27011e..e9775baff 100644 --- a/models/server/ExporterExcel.js +++ b/models/server/ExporterExcel.js @@ -1,6 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; import { createWorkbook } from './createWorkbook'; import { formatDateTime, @@ -31,7 +30,7 @@ class ExporterExcel { this.userLanguage = userLanguage; } - async build(res) { + build(res) { const fs = Npm.require('fs'); const os = Npm.require('os'); const path = Npm.require('path'); @@ -56,16 +55,16 @@ class ExporterExcel { }; _.extend( result, - await ReactiveCache.getBoard(this._boardId, { + ReactiveCache.getBoard(this._boardId, { fields: { stars: 0, }, }), ); - result.lists = await ReactiveCache.getLists(byBoard, noBoardId); - result.cards = await ReactiveCache.getCards(byBoardNoLinked, noBoardId); - result.swimlanes = await ReactiveCache.getSwimlanes(byBoard, noBoardId); - result.customFields = await ReactiveCache.getCustomFields( + result.lists = ReactiveCache.getLists(byBoard, noBoardId); + result.cards = ReactiveCache.getCards(byBoardNoLinked, noBoardId); + result.swimlanes = ReactiveCache.getSwimlanes(byBoard, noBoardId); + result.customFields = ReactiveCache.getCustomFields( { boardIds: { $in: [this.boardId], @@ -77,34 +76,34 @@ class ExporterExcel { }, }, ); - result.comments = await ReactiveCache.getCardComments(byBoard, noBoardId); - result.activities = await ReactiveCache.getActivities(byBoard, noBoardId); - result.rules = await ReactiveCache.getRules(byBoard, noBoardId); + result.comments = ReactiveCache.getCardComments(byBoard, noBoardId); + result.activities = ReactiveCache.getActivities(byBoard, noBoardId); + result.rules = ReactiveCache.getRules(byBoard, noBoardId); result.checklists = []; result.checklistItems = []; result.subtaskItems = []; result.triggers = []; result.actions = []; - for (const card of result.cards) { + result.cards.forEach((card) => { result.checklists.push( - ...await ReactiveCache.getChecklists({ + ...ReactiveCache.getChecklists({ cardId: card._id, }), ); result.checklistItems.push( - ...await ReactiveCache.getChecklistItems({ + ...ReactiveCache.getChecklistItems({ cardId: card._id, }), ); result.subtaskItems.push( - ...await ReactiveCache.getCards({ + ...ReactiveCache.getCards({ parentId: card._id, }), ); - } - for (const rule of result.rules) { + }); + result.rules.forEach((rule) => { result.triggers.push( - ...await ReactiveCache.getTriggers( + ...ReactiveCache.getTriggers( { _id: rule.triggerId, }, @@ -112,14 +111,14 @@ class ExporterExcel { ), ); result.actions.push( - ...await ReactiveCache.getActions( + ...ReactiveCache.getActions( { _id: rule.actionId, }, noBoardId, ), ); - } + }); // we also have to export some user data - as the other elements only // include id but we have to be careful: @@ -169,7 +168,7 @@ class ExporterExcel { 'profile.avatarUrl': 1, }, }; - result.users = (await ReactiveCache.getUsers(byUserIds, userFields)) + result.users = ReactiveCache.getUsers(byUserIds, userFields) .map((user) => { // user avatar is stored as a relative url, we export absolute if ((user.profile || {}).avatarUrl) { @@ -905,8 +904,8 @@ class ExporterExcel { workbook.xlsx.write(res).then(function () {}); } - async canExport(user) { - const board = await ReactiveCache.getBoard(this._boardId); + canExport(user) { + const board = ReactiveCache.getBoard(this._boardId); return board && board.isVisibleBy(user); } } diff --git a/models/server/metrics.js b/models/server/metrics.js index af4c9eca0..669bbbf92 100644 --- a/models/server/metrics.js +++ b/models/server/metrics.js @@ -57,12 +57,12 @@ const getBoardTitleWithMostActivities = (dateWithXdaysAgo, nbLimit) => { ); }; -const getBoards = async (boardIds) => { - const ret = await ReactiveCache.getBoards({ _id: { $in: boardIds } }); +const getBoards = (boardIds) => { + const ret = ReactiveCache.getBoards({ _id: { $in: boardIds } }); return ret; }; Meteor.startup(() => { - WebApp.connectHandlers.use('/metrics', async (req, res, next) => { + WebApp.connectHandlers.use('/metrics', (req, res, next) => { try { const ipAddress = req.headers['x-forwarded-for'] || req.socket.remoteAddress; @@ -95,7 +95,7 @@ Meteor.startup(() => { metricsRes += '# Number of registered users\n'; // Get number of registered user - resCount = (await ReactiveCache.getUsers({})).length; // KPI 2 + resCount = ReactiveCache.getUsers({}).length; // KPI 2 metricsRes += 'wekan_registeredUsers ' + resCount + '\n'; resCount = 0; @@ -103,7 +103,7 @@ Meteor.startup(() => { metricsRes += '# Number of registered boards\n'; // Get number of registered boards - resCount = (await ReactiveCache.getBoards({ archived: false, type: 'board' })).length; // KPI 3 + resCount = ReactiveCache.getBoards({ archived: false, type: 'board' }).length; // KPI 3 metricsRes += 'wekan_registeredboards ' + resCount + '\n'; resCount = 0; @@ -112,8 +112,8 @@ Meteor.startup(() => { // Get number of registered boards by registered users resCount = - (await ReactiveCache.getBoards({ archived: false, type: 'board' })).length / - (await ReactiveCache.getUsers({})).length; // KPI 4 + ReactiveCache.getBoards({ archived: false, type: 'board' }).length / + ReactiveCache.getUsers({}).length; // KPI 4 metricsRes += 'wekan_registeredboardsBysRegisteredUsers ' + resCount + '\n'; resCount = 0; @@ -122,11 +122,11 @@ Meteor.startup(() => { metricsRes += '# Number of registered boards\n'; // Get board numbers with only one member - resCount = (await ReactiveCache.getBoards({ + resCount = ReactiveCache.getBoards({ archived: false, type: 'board', members: { $size: 1 }, - })).length; // KPI 5 + }).length; // KPI 5 metricsRes += 'wekan_registeredboardsWithOnlyOneMember ' + resCount + '\n'; resCount = 0; @@ -144,9 +144,9 @@ Meteor.startup(() => { let dateWithXdaysAgo = new Date( new Date() - xdays * 24 * 60 * 60 * 1000, ); - resCount = (await ReactiveCache.getUsers({ + resCount = ReactiveCache.getUsers({ lastConnectionDate: { $gte: dateWithXdaysAgo }, - })).length; // KPI 5 + }).length; // KPI 5 metricsRes += 'wekan_usersWithLastConnectionDated5DaysAgo ' + resCount + '\n'; resCount = 0; @@ -157,9 +157,9 @@ Meteor.startup(() => { // Get number of users with last connection dated 10 days ago xdays = 10; dateWithXdaysAgo = new Date(new Date() - xdays * 24 * 60 * 60 * 1000); - resCount = (await ReactiveCache.getUsers({ + resCount = ReactiveCache.getUsers({ lastConnectionDate: { $gte: dateWithXdaysAgo }, - })).length; // KPI 5 + }).length; // KPI 5 metricsRes += 'wekan_usersWithLastConnectionDated10DaysAgo ' + resCount + '\n'; resCount = 0; @@ -170,9 +170,9 @@ Meteor.startup(() => { // Get number of users with last connection dated 20 days ago xdays = 20; dateWithXdaysAgo = new Date(new Date() - xdays * 24 * 60 * 60 * 1000); - resCount = (await ReactiveCache.getUsers({ + resCount = ReactiveCache.getUsers({ lastConnectionDate: { $gte: dateWithXdaysAgo }, - })).length; // KPI 5 + }).length; // KPI 5 metricsRes += 'wekan_usersWithLastConnectionDated20DaysAgo ' + resCount + '\n'; resCount = 0; @@ -183,9 +183,9 @@ Meteor.startup(() => { // Get number of users with last connection dated 20 days ago xdays = 30; dateWithXdaysAgo = new Date(new Date() - xdays * 24 * 60 * 60 * 1000); - resCount = (await ReactiveCache.getUsers({ + resCount = ReactiveCache.getUsers({ lastConnectionDate: { $gte: dateWithXdaysAgo }, - })).length; // KPI 5 + }).length; // KPI 5 metricsRes += 'wekan_usersWithLastConnectionDated30DaysAgo ' + resCount + '\n'; resCount = 0; diff --git a/models/settings.js b/models/settings.js index 00dd1b653..00349c762 100644 --- a/models/settings.js +++ b/models/settings.js @@ -1,6 +1,5 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; //var nodemailer = require('nodemailer'); // Sandstorm context is detected using the METEOR_SETTINGS environment variable @@ -118,34 +117,6 @@ Settings.attachSchema( type: String, optional: true, }, - customHeadEnabled: { - type: Boolean, - optional: true, - }, - customHeadMetaTags: { - type: String, - optional: true, - }, - customHeadLinkTags: { - type: String, - optional: true, - }, - customManifestEnabled: { - type: Boolean, - optional: true, - }, - customManifestContent: { - type: String, - optional: true, - }, - customAssetLinksEnabled: { - type: Boolean, - optional: true, - }, - customAssetLinksContent: { - type: String, - optional: true, - }, accessibilityPageEnabled: { type: Boolean, optional: true, @@ -159,28 +130,6 @@ Settings.attachSchema( type: String, optional: true, }, - supportPopupText: { - type: String, - optional: true, - }, - supportPageEnabled: { - type: Boolean, - optional: true, - defaultValue: false, - }, - supportPagePublic: { - type: Boolean, - optional: true, - defaultValue: false, - }, - supportTitle: { - type: String, - optional: true, - }, - supportPageText: { - type: String, - optional: true, - }, createdAt: { type: Date, denyUpdate: true, @@ -224,15 +173,15 @@ Settings.helpers({ }); Settings.allow({ update(userId) { - const user = Meteor.users.findOne(userId); + const user = ReactiveCache.getUser(userId); return user && user.isAdmin; }, }); if (Meteor.isServer) { - Meteor.startup(async () => { - await Settings._collection.createIndexAsync({ modifiedAt: -1 }); - const setting = await ReactiveCache.getCurrentSetting(); + Meteor.startup(() => { + Settings._collection.createIndex({ modifiedAt: -1 }); + const setting = ReactiveCache.getCurrentSetting(); if (!setting) { const now = new Date(); const domain = process.env.ROOT_URL.match( @@ -258,7 +207,7 @@ if (Meteor.isServer) { } if (isSandstorm) { // At Sandstorm, Admin Panel has SMTP settings - const newSetting = await ReactiveCache.getCurrentSetting(); + const newSetting = ReactiveCache.getCurrentSetting(); if (!process.env.MAIL_URL && newSetting.mailUrl()) process.env.MAIL_URL = newSetting.mailUrl(); Accounts.emailTemplates.from = process.env.MAIL_FROM @@ -312,16 +261,15 @@ if (Meteor.isServer) { return config; } - async function sendInvitationEmail(_id) { - const icode = await ReactiveCache.getInvitationCode(_id); - const author = await ReactiveCache.getCurrentUser(); + function sendInvitationEmail(_id) { + const icode = ReactiveCache.getInvitationCode(_id); + const author = ReactiveCache.getCurrentUser(); try { - const authorUser = await ReactiveCache.getUser(icode.authorId); - const fullName = authorUser?.profile?.fullname || ""; + const fullName = ReactiveCache.getUser(icode.authorId)?.profile?.fullname || ""; const params = { email: icode.email, - inviter: fullName != "" ? fullName + " (" + authorUser.username + " )" : authorUser.username, + inviter: fullName != "" ? fullName + " (" + ReactiveCache.getUser(icode.authorId).username + " )" : ReactiveCache.getUser(icode.authorId).username, user: icode.email.split('@')[0], icode: icode.code, url: FlowRouter.url('sign-up'), @@ -352,8 +300,8 @@ if (Meteor.isServer) { } } - async function isNonAdminAllowedToSendMail(currentUser){ - const currSett = await ReactiveCache.getCurrentSetting(); + function isNonAdminAllowedToSendMail(currentUser){ + const currSett = ReactiveCache.getCurrentSetting(); let isAllowed = false; if(currSett && currSett != undefined && currSett.disableRegistration && currSett.mailDomainName !== undefined && currSett.mailDomainName != ""){ for(let i = 0; i < currentUser.emails.length; i++) { @@ -390,20 +338,20 @@ if (Meteor.isServer) { } Meteor.methods({ - async sendInvitation(emails, boards) { + sendInvitation(emails, boards) { let rc = 0; check(emails, [String]); check(boards, [String]); - const user = await ReactiveCache.getCurrentUser(); - if (!user.isAdmin && !(await isNonAdminAllowedToSendMail(user))) { + const user = ReactiveCache.getCurrentUser(); + if (!user.isAdmin && !isNonAdminAllowedToSendMail(user)) { rc = -1; throw new Meteor.Error('not-allowed'); } - for (const email of emails) { + emails.forEach(email => { if (email && SimpleSchema.RegEx.Email.test(email)) { // Checks if the email is already link to an account. - const userExist = await ReactiveCache.getUser({ email }); + const userExist = ReactiveCache.getUser({ email }); if (userExist) { rc = -1; throw new Meteor.Error( @@ -412,12 +360,12 @@ if (Meteor.isServer) { ); } // Checks if the email is already link to an invitation. - const invitation = await ReactiveCache.getInvitationCode({ email }); + const invitation = ReactiveCache.getInvitationCode({ email }); if (invitation) { InvitationCodes.update(invitation, { $set: { boardsToBeInvited: boards }, }); - await sendInvitationEmail(invitation._id); + sendInvitationEmail(invitation._id); } else { const code = getRandomNum(100000, 999999); InvitationCodes.insert( @@ -428,9 +376,9 @@ if (Meteor.isServer) { createdAt: new Date(), authorId: Meteor.userId(), }, - async function(err, _id) { + function(err, _id) { if (!err && _id) { - await sendInvitationEmail(_id); + sendInvitationEmail(_id); } else { rc = -1; throw new Meteor.Error( @@ -442,15 +390,15 @@ if (Meteor.isServer) { ); } } - } + }); return rc; }, - async sendSMTPTestEmail() { + sendSMTPTestEmail() { if (!Meteor.userId()) { throw new Meteor.Error('invalid-user'); } - const user = await ReactiveCache.getCurrentUser(); + const user = ReactiveCache.getCurrentUser(); if (!user.emails || !user.emails[0] || !user.emails[0].address) { throw new Meteor.Error('email-invalid'); } @@ -500,8 +448,8 @@ if (Meteor.isServer) { }; }, - async getCustomUI() { - const setting = await ReactiveCache.getCurrentSetting(); + getCustomUI() { + const setting = ReactiveCache.getCurrentSetting(); if (!setting.productName) { return { productName: '', @@ -513,8 +461,8 @@ if (Meteor.isServer) { } }, - async isDisableRegistration() { - const setting = await ReactiveCache.getCurrentSetting(); + isDisableRegistration() { + const setting = ReactiveCache.getCurrentSetting(); if (setting.disableRegistration === true) { return true; } else { @@ -522,8 +470,8 @@ if (Meteor.isServer) { } }, - async isDisableForgotPassword() { - const setting = await ReactiveCache.getCurrentSetting(); + isDisableForgotPassword() { + const setting = ReactiveCache.getCurrentSetting(); if (setting.disableForgotPassword === true) { return true; } else { diff --git a/models/swimlanes.js b/models/swimlanes.js index 8b41a0292..659111d04 100644 --- a/models/swimlanes.js +++ b/models/swimlanes.js @@ -108,51 +108,36 @@ Swimlanes.attachSchema( type: String, defaultValue: 'swimlane', }, - height: { + collapsed: { /** - * The height of the swimlane in pixels. - * -1 = auto-height (default) - * 50-2000 = fixed height in pixels + * is the swimlane collapsed */ - type: Number, - optional: true, - defaultValue: -1, - custom() { - const h = this.value; - if (h !== -1 && (h < 50 || h > 2000)) { - return 'heightOutOfRange'; - } - }, + type: Boolean, + defaultValue: false, }, - // NOTE: collapsed state is per-user only, stored in user profile.collapsedSwimlanes - // and localStorage for non-logged-in users - // NOTE: height is per-board (shared with all users), stored in swimlanes.height }), ); Swimlanes.allow({ insert(userId, doc) { - // ReadOnly and CommentOnly users cannot create swimlanes - return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMemberCommentOnly(userId, ReactiveCache.getBoard(doc.boardId)); }, update(userId, doc) { - // ReadOnly and CommentOnly users cannot edit swimlanes - return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMemberCommentOnly(userId, ReactiveCache.getBoard(doc.boardId)); }, remove(userId, doc) { - // ReadOnly and CommentOnly users cannot delete swimlanes - return allowIsBoardMemberWithWriteAccess(userId, Boards.findOne(doc.boardId)); + return allowIsBoardMemberCommentOnly(userId, ReactiveCache.getBoard(doc.boardId)); }, fetch: ['boardId'], }); Swimlanes.helpers({ - async copy(boardId) { + copy(boardId) { const oldId = this._id; const oldBoardId = this.boardId; this.boardId = boardId; delete this._id; - const _id = await Swimlanes.insertAsync(this); + const _id = Swimlanes.insert(this); const query = { swimlaneId: { $in: [oldId, ''] }, @@ -163,20 +148,17 @@ Swimlanes.helpers({ } // Copy all lists in swimlane - const lists = await ReactiveCache.getLists(query); - for (const list of lists) { + ReactiveCache.getLists(query).forEach(list => { list.type = 'list'; list.swimlaneId = oldId; list.boardId = boardId; - await list.copy(boardId, _id); - } - - return _id; + list.copy(boardId, _id); + }); }, - async move(toBoardId) { - for (const list of await this.lists()) { - const toList = await ReactiveCache.getList({ + move(toBoardId) { + this.lists().forEach(list => { + const toList = ReactiveCache.getList({ boardId: toBoardId, title: list.title, archived: false, @@ -186,33 +168,32 @@ Swimlanes.helpers({ if (toList) { toListId = toList._id; } else { - toListId = await Lists.insertAsync({ + toListId = Lists.insert({ title: list.title, boardId: toBoardId, type: list.type, archived: false, wipLimit: list.wipLimit, - swimlaneId: this._id, + swimlaneId: toSwimlaneId, // Set the target swimlane for the copied list }); } - const cards = await ReactiveCache.getCards({ + ReactiveCache.getCards({ listId: list._id, swimlaneId: this._id, + }).forEach(card => { + card.move(toBoardId, this._id, toListId); }); - for (const card of cards) { - await card.move(toBoardId, this._id, toListId); - } - } + }); - await Swimlanes.updateAsync(this._id, { + Swimlanes.update(this._id, { $set: { boardId: toBoardId, }, }); // make sure there is a default swimlane - (await this.board()).getDefaultSwimline(); + this.board().getDefaultSwimline(); }, cards() { @@ -252,14 +233,11 @@ Swimlanes.helpers({ myLists() { // Return per-swimlane lists: provide lists specific to this swimlane - return ReactiveCache.getLists( - { - boardId: this.boardId, - swimlaneId: this._id, - archived: false - }, - { sort: ['sort'] }, - ); + return ReactiveCache.getLists({ + boardId: this.boardId, + swimlaneId: this._id, + archived: false + }); }, allCards() { @@ -268,21 +246,6 @@ Swimlanes.helpers({ }, isCollapsed() { - if (Meteor.isClient) { - const user = ReactiveCache.getCurrentUser(); - if (user && user.getCollapsedSwimlaneFromStorage) { - const stored = user.getCollapsedSwimlaneFromStorage(this.boardId, this._id); - if (typeof stored === 'boolean') { - return stored; - } - } - if (!user && Users.getPublicCollapsedSwimlane) { - const stored = Users.getPublicCollapsedSwimlane(this.boardId, this._id); - if (typeof stored === 'boolean') { - return stored; - } - } - } return this.collapsed === true; }, @@ -318,59 +281,64 @@ Swimlanes.helpers({ return (user.profile || {}).boardTemplatesSwimlaneId === this._id; }, - async remove() { - return await Swimlanes.removeAsync({ _id: this._id }); - }, - - async rename(title) { - return await Swimlanes.updateAsync(this._id, { $set: { title } }); - }, - - // NOTE: collapse() removed - collapsed state is per-user only - // Use user.setCollapsedSwimlane(boardId, swimlaneId, collapsed) instead - - async archive() { - if (this.isTemplateSwimlane()) { - for (const list of await this.myLists()) { - await list.archive(); - } - } - return await Swimlanes.updateAsync(this._id, { $set: { archived: true, archivedAt: new Date() } }); - }, - - async restore() { - if (this.isTemplateSwimlane()) { - for (const list of await this.myLists()) { - await list.restore(); - } - } - return await Swimlanes.updateAsync(this._id, { $set: { archived: false } }); - }, - - async setColor(newColor) { - return await Swimlanes.updateAsync(this._id, { $set: { color: newColor } }); + remove() { + Swimlanes.remove({ _id: this._id }); }, }); -Swimlanes.userArchivedSwimlanes = async userId => { - return await ReactiveCache.getSwimlanes({ - boardId: { $in: await Boards.userBoardIds(userId, null) }, +Swimlanes.mutations({ + rename(title) { + return { $set: { title } }; + }, + + collapse(enable = true) { + return { $set: { collapsed: !!enable } }; + }, + + archive() { + if (this.isTemplateSwimlane()) { + this.myLists().forEach(list => { + return list.archive(); + }); + } + return { $set: { archived: true, archivedAt: new Date() } }; + }, + + restore() { + if (this.isTemplateSwimlane()) { + this.myLists().forEach(list => { + return list.restore(); + }); + } + return { $set: { archived: false } }; + }, + + setColor(newColor) { + return { + $set: { + color: newColor, + }, + }; + }, +}); + +Swimlanes.userArchivedSwimlanes = userId => { + return ReactiveCache.getSwimlanes({ + boardId: { $in: Boards.userBoardIds(userId, null) }, archived: true, }) }; -Swimlanes.userArchivedSwimlaneIds = async () => { - const swimlanes = await Swimlanes.userArchivedSwimlanes(); - return swimlanes.map(swim => { return swim._id; }); +Swimlanes.userArchivedSwimlaneIds = () => { + return Swimlanes.userArchivedSwimlanes().map(swim => { return swim._id; }); }; -Swimlanes.archivedSwimlanes = async () => { - return await ReactiveCache.getSwimlanes({ archived: true }); +Swimlanes.archivedSwimlanes = () => { + return ReactiveCache.getSwimlanes({ archived: true }); }; -Swimlanes.archivedSwimlaneIds = async () => { - const swimlanes = await Swimlanes.archivedSwimlanes(); - return swimlanes.map(swim => { +Swimlanes.archivedSwimlaneIds = () => { + return Swimlanes.archivedSwimlanes().map(swim => { return swim._id; }); }; @@ -378,9 +346,9 @@ Swimlanes.archivedSwimlaneIds = async () => { Swimlanes.hookOptions.after.update = { fetchPrevious: false }; if (Meteor.isServer) { - Meteor.startup(async () => { - await Swimlanes._collection.createIndexAsync({ modifiedAt: -1 }); - await Swimlanes._collection.createIndexAsync({ boardId: 1 }); + Meteor.startup(() => { + Swimlanes._collection.createIndex({ modifiedAt: -1 }); + Swimlanes._collection.createIndex({ boardId: 1 }); }); Swimlanes.after.insert((userId, doc) => { @@ -401,8 +369,8 @@ if (Meteor.isServer) { }, 100); }); - Swimlanes.before.remove(async function(userId, doc) { - const lists = await ReactiveCache.getLists( + Swimlanes.before.remove(function(userId, doc) { + const lists = ReactiveCache.getLists( { boardId: doc.boardId, swimlaneId: { $in: [doc._id, ''] }, @@ -412,14 +380,14 @@ if (Meteor.isServer) { ); if (lists.length < 2) { - for (const list of lists) { - await list.remove(); - } + lists.forEach(list => { + list.remove(); + }); } else { - await Cards.removeAsync({ swimlaneId: doc._id }); + Cards.remove({ swimlaneId: doc._id }); } - await Activities.insertAsync({ + Activities.insert({ userId, type: 'swimlane', activityType: 'removeSwimlane', @@ -478,15 +446,14 @@ if (Meteor.isServer) { * @return_type [{_id: string, * title: string}] */ - JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes', async function(req, res) { + JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes', function(req, res) { try { const paramBoardId = req.params.boardId; Authentication.checkBoardAccess(req.userId, paramBoardId); - const swimlanes = await ReactiveCache.getSwimlanes({ boardId: paramBoardId, archived: false }); JsonRoutes.sendResult(res, { code: 200, - data: swimlanes.map( + data: ReactiveCache.getSwimlanes({ boardId: paramBoardId, archived: false }).map( function(doc) { return { _id: doc._id, @@ -512,7 +479,7 @@ if (Meteor.isServer) { * @param {string} swimlaneId the ID of the swimlane * @return_type Swimlanes */ - JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes/:swimlaneId', async function( + JsonRoutes.add('GET', '/api/boards/:boardId/swimlanes/:swimlaneId', function( req, res, ) { @@ -523,7 +490,7 @@ if (Meteor.isServer) { JsonRoutes.sendResult(res, { code: 200, - data: await ReactiveCache.getSwimlane({ + data: ReactiveCache.getSwimlane({ _id: paramSwimlaneId, boardId: paramBoardId, archived: false, @@ -546,13 +513,13 @@ if (Meteor.isServer) { * @param {string} title the new title of the swimlane * @return_type {_id: string} */ - JsonRoutes.add('POST', '/api/boards/:boardId/swimlanes', async function(req, res) { + JsonRoutes.add('POST', '/api/boards/:boardId/swimlanes', function(req, res) { try { const paramBoardId = req.params.boardId; - Authentication.checkBoardWriteAccess(req.userId, paramBoardId); + Authentication.checkBoardAccess(req.userId, paramBoardId); - const board = await ReactiveCache.getBoard(paramBoardId); - const id = await Swimlanes.insertAsync({ + const board = ReactiveCache.getBoard(paramBoardId); + const id = Swimlanes.insert({ title: req.body.title, boardId: paramBoardId, sort: board.swimlanes().length, @@ -581,13 +548,13 @@ if (Meteor.isServer) { * @param {string} title the new title of the swimlane * @return_type {_id: string} */ - JsonRoutes.add('PUT', '/api/boards/:boardId/swimlanes/:swimlaneId', async function(req, res) { + JsonRoutes.add('PUT', '/api/boards/:boardId/swimlanes/:swimlaneId', function(req, res) { try { const paramBoardId = req.params.boardId; const paramSwimlaneId = req.params.swimlaneId; - Authentication.checkBoardWriteAccess(req.userId, paramBoardId); - const board = await ReactiveCache.getBoard(paramBoardId); - const swimlane = await ReactiveCache.getSwimlane({ + Authentication.checkBoardAccess(req.userId, paramBoardId); + const board = ReactiveCache.getBoard(paramBoardId); + const swimlane = ReactiveCache.getSwimlane({ _id: paramSwimlaneId, boardId: paramBoardId, }); @@ -630,7 +597,7 @@ if (Meteor.isServer) { try { const paramBoardId = req.params.boardId; const paramSwimlaneId = req.params.swimlaneId; - Authentication.checkBoardWriteAccess(req.userId, paramBoardId); + Authentication.checkBoardAccess(req.userId, paramBoardId); Swimlanes.remove({ _id: paramSwimlaneId, boardId: paramBoardId }); JsonRoutes.sendResult(res, { code: 200, @@ -693,7 +660,7 @@ Swimlanes.helpers({ hasMovedFromOriginalPosition() { const history = this.getOriginalPosition(); if (!history) return false; - + return history.originalPosition.sort !== this.sort; }, @@ -703,7 +670,7 @@ Swimlanes.helpers({ getOriginalPositionDescription() { const history = this.getOriginalPosition(); if (!history) return 'No original position data'; - + return `Original position: ${history.originalPosition.sort || 0}`; }, }); diff --git a/models/tableVisibilityModeSettings.js b/models/tableVisibilityModeSettings.js index 157152783..3bee121e2 100644 --- a/models/tableVisibilityModeSettings.js +++ b/models/tableVisibilityModeSettings.js @@ -46,14 +46,14 @@ TableVisibilityModeSettings.attachSchema( TableVisibilityModeSettings.allow({ update(userId) { - const user = Meteor.users.findOne(userId); + const user = ReactiveCache.getUser(userId); return user && user.isAdmin; }, }); if (Meteor.isServer) { - Meteor.startup(async () => { - await TableVisibilityModeSettings._collection.createIndexAsync({ modifiedAt: -1 }); + Meteor.startup(() => { + TableVisibilityModeSettings._collection.createIndex({ modifiedAt: -1 }); TableVisibilityModeSettings.upsert( { _id: 'tableVisibilityMode-allowPrivateOnly' }, { diff --git a/models/team.js b/models/team.js index 38f819aba..b39298cd7 100644 --- a/models/team.js +++ b/models/team.js @@ -79,7 +79,7 @@ Team.attachSchema( if (Meteor.isServer) { Team.allow({ insert(userId, doc) { - const user = Meteor.users.findOne(userId); + const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser(); if (user?.isAdmin) return true; if (!user) { @@ -88,7 +88,7 @@ if (Meteor.isServer) { return doc._id === userId; }, update(userId, doc) { - const user = Meteor.users.findOne(userId); + const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser(); if (user?.isAdmin) return true; if (!user) { @@ -97,7 +97,7 @@ if (Meteor.isServer) { return doc._id === userId; }, remove(userId, doc) { - const user = Meteor.users.findOne(userId); + const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser(); if (user?.isAdmin) return true; if (!user) { @@ -109,21 +109,21 @@ if (Meteor.isServer) { }); Meteor.methods({ - async setCreateTeam( + setCreateTeam( teamDisplayName, teamDesc, teamShortName, teamWebsite, teamIsActive, ) { - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { check(teamDisplayName, String); check(teamDesc, String); check(teamShortName, String); check(teamWebsite, String); check(teamIsActive, Boolean); - const nTeamNames = (await ReactiveCache.getTeams({ teamShortName })).length; + const nTeamNames = ReactiveCache.getTeams({ teamShortName }).length; if (nTeamNames > 0) { throw new Meteor.Error('teamname-already-taken'); } else { @@ -137,7 +137,7 @@ if (Meteor.isServer) { } } }, - async setCreateTeamFromOidc( + setCreateTeamFromOidc( teamDisplayName, teamDesc, teamShortName, @@ -149,7 +149,7 @@ if (Meteor.isServer) { check(teamShortName, String); check(teamWebsite, String); check(teamIsActive, Boolean); - const nTeamNames = (await ReactiveCache.getTeams({ teamShortName })).length; + const nTeamNames = ReactiveCache.getTeams({ teamShortName }).length; if (nTeamNames > 0) { throw new Meteor.Error('teamname-already-taken'); } else { @@ -162,19 +162,19 @@ if (Meteor.isServer) { }); } }, - async setTeamDisplayName(team, teamDisplayName) { - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + setTeamDisplayName(team, teamDisplayName) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { check(team, Object); check(teamDisplayName, String); Team.update(team, { $set: { teamDisplayName: teamDisplayName }, }); - await Meteor.callAsync('setUsersTeamsTeamDisplayName', team._id, teamDisplayName); + Meteor.call('setUsersTeamsTeamDisplayName', team._id, teamDisplayName); } }, - async setTeamDesc(team, teamDesc) { - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + setTeamDesc(team, teamDesc) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { check(team, Object); check(teamDesc, String); Team.update(team, { @@ -183,8 +183,8 @@ if (Meteor.isServer) { } }, - async setTeamShortName(team, teamShortName) { - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + setTeamShortName(team, teamShortName) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { check(team, Object); check(teamShortName, String); Team.update(team, { @@ -193,8 +193,8 @@ if (Meteor.isServer) { } }, - async setTeamIsActive(team, teamIsActive) { - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + setTeamIsActive(team, teamIsActive) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { check(team, Object); check(teamIsActive, Boolean); Team.update(team, { @@ -202,7 +202,7 @@ if (Meteor.isServer) { }); } }, - async setTeamAllFieldsFromOidc( + setTeamAllFieldsFromOidc( team, teamDisplayName, teamDesc, @@ -225,9 +225,9 @@ if (Meteor.isServer) { teamIsActive: teamIsActive, }, }); - await Meteor.callAsync('setUsersTeamsTeamDisplayName', team._id, teamDisplayName); + Meteor.call('setUsersTeamsTeamDisplayName', team._id, teamDisplayName); }, - async setTeamAllFields( + setTeamAllFields( team, teamDisplayName, teamDesc, @@ -235,7 +235,7 @@ if (Meteor.isServer) { teamWebsite, teamIsActive, ) { - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { check(team, Object); check(teamDisplayName, String); check(teamDesc, String); @@ -251,7 +251,7 @@ if (Meteor.isServer) { teamIsActive: teamIsActive, }, }); - await Meteor.callAsync('setUsersTeamsTeamDisplayName', team._id, teamDisplayName); + Meteor.call('setUsersTeamsTeamDisplayName', team._id, teamDisplayName); } }, }); @@ -259,8 +259,8 @@ if (Meteor.isServer) { if (Meteor.isServer) { // Index for Team name. - Meteor.startup(async () => { - await Team._collection.createIndexAsync({ teamDisplayName: 1 }); + Meteor.startup(() => { + Team._collection.createIndex({ teamDisplayName: 1 }); }); } diff --git a/models/translation.js b/models/translation.js index 4a12c5389..b473ff2b7 100644 --- a/models/translation.js +++ b/models/translation.js @@ -59,7 +59,7 @@ Translation.attachSchema( if (Meteor.isServer) { Translation.allow({ insert(userId, doc) { - const user = Meteor.users.findOne(userId); + const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser(); if (user?.isAdmin) return true; if (!user) { @@ -68,7 +68,7 @@ if (Meteor.isServer) { return doc._id === userId; }, update(userId, doc) { - const user = Meteor.users.findOne(userId); + const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser(); if (user?.isAdmin) return true; if (!user) { @@ -77,7 +77,7 @@ if (Meteor.isServer) { return doc._id === userId; }, remove(userId, doc) { - const user = Meteor.users.findOne(userId); + const user = ReactiveCache.getUser(userId) || ReactiveCache.getCurrentUser(); if (user?.isAdmin) return true; if (!user) { @@ -89,7 +89,7 @@ if (Meteor.isServer) { }); Meteor.methods({ - async setCreateTranslation( + setCreateTranslation( language, text, translationText, @@ -98,11 +98,7 @@ if (Meteor.isServer) { check(text, String); check(translationText, String); - if (!(await ReactiveCache.getCurrentUser())?.isAdmin) { - throw new Meteor.Error('not-authorized'); - } - - const nTexts = (await ReactiveCache.getTranslations({ language, text })).length; + const nTexts = ReactiveCache.getTranslations({ language, text }).length; if (nTexts > 0) { throw new Meteor.Error('text-already-taken'); } else { @@ -113,34 +109,20 @@ if (Meteor.isServer) { }); } }, - async setTranslationText(translation, translationText) { + setTranslationText(translation, translationText) { check(translation, Object); check(translationText, String); - - if (!(await ReactiveCache.getCurrentUser())?.isAdmin) { - throw new Meteor.Error('not-authorized'); - } - Translation.update(translation, { $set: { translationText: translationText }, }); }, - async deleteTranslation(translationId) { - check(translationId, String); - - if (!(await ReactiveCache.getCurrentUser())?.isAdmin) { - throw new Meteor.Error('not-authorized'); - } - - Translation.remove(translationId); - }, }); } if (Meteor.isServer) { // Index for Organization User. - Meteor.startup(async () => { - await Translation._collection.createIndexAsync({ modifiedAt: -1 }); + Meteor.startup(() => { + Translation._collection.createIndex({ modifiedAt: -1 }); }); } diff --git a/models/trelloCreator.js b/models/trelloCreator.js index d68f5966d..5dde821c0 100644 --- a/models/trelloCreator.js +++ b/models/trelloCreator.js @@ -1,28 +1,25 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { TAPi18n } from '/imports/i18n'; -import { CustomFields } from './customFields'; -import { - formatDateTime, - formatDate, - formatTime, - getISOWeek, - isValidDate, - isBefore, - isAfter, - isSame, - add, - subtract, - startOf, - endOf, - format, - parseDate, - now, - createDate, - fromNow, - calendar +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar } from '/imports/lib/dateUtils'; -import getSlug from 'limax'; -import { validateAttachmentUrl } from './lib/attachmentUrlValidation'; const DateString = Match.Where(function(dateAsString) { check(dateAsString, String); @@ -182,7 +179,7 @@ export class TrelloCreator { } // You must call parseActions before calling this one. - async createBoardAndLabels(trelloBoard) { + createBoardAndLabels(trelloBoard) { let color = 'blue'; if (this.getColor(trelloBoard.prefs.background) !== undefined) { color = this.getColor(trelloBoard.prefs.background); @@ -208,7 +205,7 @@ export class TrelloCreator { permission: this.getPermission(trelloBoard.prefs.permissionLevel), slug: getSlug(trelloBoard.name) || 'board', stars: 0, - title: await Boards.uniqueTitle(trelloBoard.name), + title: Boards.uniqueTitle(trelloBoard.name), }; // now add other members if (trelloBoard.memberships) { @@ -472,17 +469,6 @@ export class TrelloCreator { } }; if (att.url) { - const validation = validateAttachmentUrl(att.url); - if (!validation.valid) { - if (process.env.DEBUG === 'true') { - console.warn( - 'Blocked attachment URL during Trello import:', - validation.reason, - att.url, - ); - } - return; - } Attachments.load(att.url, opts, cb, true); } else if (att.file) { Attachments.insert(att.file, opts, cb, true); @@ -780,18 +766,18 @@ export class TrelloCreator { } } - async create(board, currentBoardId) { + create(board, currentBoardId) { // TODO : Make isSandstorm variable global const isSandstorm = Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm; if (isSandstorm && currentBoardId) { - const currentBoard = await ReactiveCache.getBoard(currentBoardId); - await currentBoard.archive(); + const currentBoard = ReactiveCache.getBoard(currentBoardId); + currentBoard.archive(); } this.parseActions(board.actions); - const boardId = await this.createBoardAndLabels(board); + const boardId = this.createBoardAndLabels(board); this.createLists(board.lists, boardId); this.createSwimlanes(boardId); this.createCards(board.cards, boardId); diff --git a/models/triggers.js b/models/triggers.js index fd8dc045b..6983955c6 100644 --- a/models/triggers.js +++ b/models/triggers.js @@ -3,6 +3,16 @@ import { Meteor } from 'meteor/meteor'; Triggers = new Mongo.Collection('triggers'); +Triggers.mutations({ + rename(description) { + return { + $set: { + description, + }, + }; + }, +}); + Triggers.before.insert((userId, doc) => { doc.createdAt = new Date(); doc.updatedAt = doc.createdAt; @@ -15,23 +25,17 @@ Triggers.before.update((userId, doc, fieldNames, modifier) => { Triggers.allow({ insert(userId, doc) { - return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId)); }, update(userId, doc) { - return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId)); }, remove(userId, doc) { - return allowIsBoardAdmin(userId, Boards.findOne(doc.boardId)); + return allowIsBoardAdmin(userId, ReactiveCache.getBoard(doc.boardId)); }, }); Triggers.helpers({ - async rename(description) { - return await Triggers.updateAsync(this._id, { - $set: { description }, - }); - }, - description() { return this.desc; }, @@ -64,8 +68,8 @@ Triggers.helpers({ }); if (Meteor.isServer) { - Meteor.startup(async () => { - await Triggers._collection.createIndexAsync({ modifiedAt: -1 }); + Meteor.startup(() => { + Triggers._collection.createIndex({ modifiedAt: -1 }); }); } diff --git a/models/unsavedEdits.js b/models/unsavedEdits.js index 71576856b..a74de60d2 100644 --- a/models/unsavedEdits.js +++ b/models/unsavedEdits.js @@ -55,9 +55,9 @@ if (Meteor.isServer) { function isAuthor(userId, doc, fieldNames = []) { return userId === doc.userId && fieldNames.indexOf('userId') === -1; } - Meteor.startup(async () => { - await UnsavedEditCollection._collection.createIndexAsync({ modifiedAt: -1 }); - await UnsavedEditCollection._collection.createIndexAsync({ userId: 1 }); + Meteor.startup(() => { + UnsavedEditCollection._collection.createIndex({ modifiedAt: -1 }); + UnsavedEditCollection._collection.createIndex({ userId: 1 }); }); UnsavedEditCollection.allow({ insert: isAuthor, diff --git a/models/userPositionHistory.js b/models/userPositionHistory.js deleted file mode 100644 index dd3ac29c5..000000000 --- a/models/userPositionHistory.js +++ /dev/null @@ -1,498 +0,0 @@ -import { ReactiveCache } from '/imports/reactiveCache'; - -/** - * UserPositionHistory collection - Per-user history of entity movements - * Similar to Activities but specifically for tracking position changes with undo/redo support - */ -UserPositionHistory = new Mongo.Collection('userPositionHistory'); - -UserPositionHistory.attachSchema( - new SimpleSchema({ - userId: { - /** - * The user who made this change - */ - type: String, - }, - boardId: { - /** - * The board where the change occurred - */ - type: String, - }, - entityType: { - /** - * Type of entity: 'swimlane', 'list', or 'card' - */ - type: String, - allowedValues: ['swimlane', 'list', 'card', 'checklist', 'checklistItem'], - }, - entityId: { - /** - * The ID of the entity that was moved - */ - type: String, - }, - actionType: { - /** - * Type of action performed - */ - type: String, - allowedValues: ['move', 'create', 'delete', 'restore', 'archive'], - }, - previousState: { - /** - * The state before the change - */ - type: Object, - blackbox: true, - optional: true, - }, - newState: { - /** - * The state after the change - */ - type: Object, - blackbox: true, - }, - // For easier undo operations, store specific fields - previousSort: { - type: Number, - decimal: true, - optional: true, - }, - newSort: { - type: Number, - decimal: true, - optional: true, - }, - previousSwimlaneId: { - type: String, - optional: true, - }, - newSwimlaneId: { - type: String, - optional: true, - }, - previousListId: { - type: String, - optional: true, - }, - newListId: { - type: String, - optional: true, - }, - previousBoardId: { - type: String, - optional: true, - }, - newBoardId: { - type: String, - optional: true, - }, - createdAt: { - /** - * When this history entry was created - */ - type: Date, - autoValue() { - if (this.isInsert) { - return new Date(); - } else if (this.isUpsert) { - return { $setOnInsert: new Date() }; - } else { - this.unset(); - } - }, - }, - // For savepoint/checkpoint feature - isCheckpoint: { - /** - * Whether this is a user-marked checkpoint/savepoint - */ - type: Boolean, - defaultValue: false, - optional: true, - }, - checkpointName: { - /** - * User-defined name for the checkpoint - */ - type: String, - optional: true, - }, - // For grouping related changes - batchId: { - /** - * ID to group related changes (e.g., moving multiple cards at once) - */ - type: String, - optional: true, - }, - }), -); - -UserPositionHistory.allow({ - insert(userId, doc) { - // Only allow users to create their own history - return userId && doc.userId === userId; - }, - update(userId, doc) { - // Only allow users to update their own history (for checkpoints) - return userId && doc.userId === userId; - }, - remove() { - // Don't allow removal - history is permanent - return false; - }, - fetch: ['userId'], -}); - -UserPositionHistory.helpers({ - /** - * Get a human-readable description of this change - */ - getDescription() { - const entityName = this.entityType; - const action = this.actionType; - - let desc = `${action} ${entityName}`; - - if (this.actionType === 'move') { - if (this.previousListId && this.newListId && this.previousListId !== this.newListId) { - desc += ' to different list'; - } else if (this.previousSwimlaneId && this.newSwimlaneId && this.previousSwimlaneId !== this.newSwimlaneId) { - desc += ' to different swimlane'; - } else if (this.previousSort !== this.newSort) { - desc += ' position'; - } - } - - return desc; - }, - - /** - * Can this change be undone? - */ - async canUndo() { - // Can undo if the entity still exists - switch (this.entityType) { - case 'card': - return !!(await ReactiveCache.getCard(this.entityId)); - case 'list': - return !!(await ReactiveCache.getList(this.entityId)); - case 'swimlane': - return !!(await ReactiveCache.getSwimlane(this.entityId)); - case 'checklist': - return !!(await ReactiveCache.getChecklist(this.entityId)); - case 'checklistItem': - return !!ChecklistItems.findOne(this.entityId); - default: - return false; - } - }, - - /** - * Undo this change - */ - async undo() { - if (!(await this.canUndo())) { - throw new Meteor.Error('cannot-undo', 'Entity no longer exists'); - } - - const userId = this.userId; - - switch (this.entityType) { - case 'card': { - const card = await ReactiveCache.getCard(this.entityId); - if (card) { - // Restore previous position - const boardId = this.previousBoardId || card.boardId; - const swimlaneId = this.previousSwimlaneId || card.swimlaneId; - const listId = this.previousListId || card.listId; - const sort = this.previousSort !== undefined ? this.previousSort : card.sort; - - Cards.update(card._id, { - $set: { - boardId, - swimlaneId, - listId, - sort, - }, - }); - } - break; - } - case 'list': { - const list = await ReactiveCache.getList(this.entityId); - if (list) { - const sort = this.previousSort !== undefined ? this.previousSort : list.sort; - const swimlaneId = this.previousSwimlaneId || list.swimlaneId; - - Lists.update(list._id, { - $set: { - sort, - swimlaneId, - }, - }); - } - break; - } - case 'swimlane': { - const swimlane = await ReactiveCache.getSwimlane(this.entityId); - if (swimlane) { - const sort = this.previousSort !== undefined ? this.previousSort : swimlane.sort; - - Swimlanes.update(swimlane._id, { - $set: { - sort, - }, - }); - } - break; - } - case 'checklist': { - const checklist = await ReactiveCache.getChecklist(this.entityId); - if (checklist) { - const sort = this.previousSort !== undefined ? this.previousSort : checklist.sort; - - Checklists.update(checklist._id, { - $set: { - sort, - }, - }); - } - break; - } - case 'checklistItem': { - if (typeof ChecklistItems !== 'undefined') { - const item = ChecklistItems.findOne(this.entityId); - if (item) { - const sort = this.previousSort !== undefined ? this.previousSort : item.sort; - const checklistId = this.previousState?.checklistId || item.checklistId; - - ChecklistItems.update(item._id, { - $set: { - sort, - checklistId, - }, - }); - } - } - break; - } - } - }, -}); - -if (Meteor.isServer) { - Meteor.startup(async () => { - await UserPositionHistory._collection.createIndexAsync({ userId: 1, boardId: 1, createdAt: -1 }); - await UserPositionHistory._collection.createIndexAsync({ userId: 1, entityType: 1, entityId: 1 }); - await UserPositionHistory._collection.createIndexAsync({ userId: 1, isCheckpoint: 1 }); - await UserPositionHistory._collection.createIndexAsync({ batchId: 1 }); - await UserPositionHistory._collection.createIndexAsync({ createdAt: 1 }); // For cleanup of old entries - }); - - /** - * Helper to track a position change - */ - UserPositionHistory.trackChange = function(options) { - const { - userId, - boardId, - entityType, - entityId, - actionType, - previousState, - newState, - batchId, - } = options; - - if (!userId || !boardId || !entityType || !entityId || !actionType) { - throw new Meteor.Error('invalid-params', 'Missing required parameters'); - } - - const historyEntry = { - userId, - boardId, - entityType, - entityId, - actionType, - newState, - }; - - if (previousState) { - historyEntry.previousState = previousState; - historyEntry.previousSort = previousState.sort; - historyEntry.previousSwimlaneId = previousState.swimlaneId; - historyEntry.previousListId = previousState.listId; - historyEntry.previousBoardId = previousState.boardId; - } - - if (newState) { - historyEntry.newSort = newState.sort; - historyEntry.newSwimlaneId = newState.swimlaneId; - historyEntry.newListId = newState.listId; - historyEntry.newBoardId = newState.boardId; - } - - if (batchId) { - historyEntry.batchId = batchId; - } - - return UserPositionHistory.insert(historyEntry); - }; - - /** - * Cleanup old history entries (keep last 1000 per user per board) - */ - UserPositionHistory.cleanup = function() { - const users = Meteor.users.find({}, { fields: { _id: 1 } }).fetch(); - - users.forEach(user => { - const boards = Boards.find({ 'members.userId': user._id }, { fields: { _id: 1 } }).fetch(); - - boards.forEach(board => { - const history = UserPositionHistory.find( - { userId: user._id, boardId: board._id, isCheckpoint: { $ne: true } }, - { sort: { createdAt: -1 }, limit: 1000 } - ).fetch(); - - if (history.length >= 1000) { - const oldestToKeep = history[999].createdAt; - - // Remove entries older than the 1000th entry (except checkpoints) - UserPositionHistory.remove({ - userId: user._id, - boardId: board._id, - createdAt: { $lt: oldestToKeep }, - isCheckpoint: { $ne: true }, - }); - } - }); - }); - }; - - // Run cleanup daily - if (Meteor.settings.public?.enableHistoryCleanup !== false) { - Meteor.setInterval(() => { - try { - UserPositionHistory.cleanup(); - } catch (e) { - console.error('Error during history cleanup:', e); - } - }, 24 * 60 * 60 * 1000); // Once per day - } -} - -// Meteor Methods for client interaction -Meteor.methods({ - 'userPositionHistory.createCheckpoint'(boardId, checkpointName) { - check(boardId, String); - check(checkpointName, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - // Create a checkpoint entry - return UserPositionHistory.insert({ - userId: this.userId, - boardId, - entityType: 'checkpoint', - entityId: 'checkpoint', - actionType: 'create', - isCheckpoint: true, - checkpointName, - newState: { - timestamp: new Date(), - }, - }); - }, - - async 'userPositionHistory.undo'(historyId) { - check(historyId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - const history = UserPositionHistory.findOne({ _id: historyId, userId: this.userId }); - if (!history) { - throw new Meteor.Error('not-found', 'History entry not found'); - } - - return await history.undo(); - }, - - 'userPositionHistory.getRecent'(boardId, limit = 50) { - check(boardId, String); - check(limit, Number); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - return UserPositionHistory.find( - { userId: this.userId, boardId }, - { sort: { createdAt: -1 }, limit: Math.min(limit, 100) } - ).fetch(); - }, - - 'userPositionHistory.getCheckpoints'(boardId) { - check(boardId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - return UserPositionHistory.find( - { userId: this.userId, boardId, isCheckpoint: true }, - { sort: { createdAt: -1 } } - ).fetch(); - }, - - async 'userPositionHistory.restoreToCheckpoint'(checkpointId) { - check(checkpointId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - const checkpoint = UserPositionHistory.findOne({ - _id: checkpointId, - userId: this.userId, - isCheckpoint: true, - }); - - if (!checkpoint) { - throw new Meteor.Error('not-found', 'Checkpoint not found'); - } - - // Find all changes after this checkpoint and undo them in reverse order - const changesToUndo = UserPositionHistory.find( - { - userId: this.userId, - boardId: checkpoint.boardId, - createdAt: { $gt: checkpoint.createdAt }, - isCheckpoint: { $ne: true }, - }, - { sort: { createdAt: -1 } } - ).fetch(); - - let undoneCount = 0; - for (const change of changesToUndo) { - try { - if (await change.canUndo()) { - await change.undo(); - undoneCount++; - } - } catch (e) { - console.warn('Failed to undo change:', change._id, e); - } - }; - - return { undoneCount, totalChanges: changesToUndo.length }; - }, -}); diff --git a/models/users.js b/models/users.js index a736c5fab..3298132a9 100644 --- a/models/users.js +++ b/models/users.js @@ -1,6 +1,5 @@ import { ReactiveCache, ReactiveMiniMongoIndex } from '/imports/reactiveCache'; -import { Random } from 'meteor/random'; -import { SyncedCron } from 'meteor/quave:synced-cron'; +import { SyncedCron } from 'meteor/percolate:synced-cron'; import { TAPi18n } from '/imports/i18n'; import ImpersonatedUsers from './impersonatedUsers'; // import { Index, MongoDBEngine } from 'meteor/easy:search'; // Temporarily disabled due to compatibility issues @@ -11,83 +10,6 @@ const isSandstorm = Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm; Users = Meteor.users; -// Public-board collapse persistence helpers (cookie-based for non-logged-in users) -if (Meteor.isClient) { - const readCookieMap = name => { - try { - const stored = typeof document !== 'undefined' ? document.cookie : ''; - const cookies = stored.split(';').map(c => c.trim()); - let json = '{}'; - for (const c of cookies) { - if (c.startsWith(name + '=')) { - json = decodeURIComponent(c.substring(name.length + 1)); - break; - } - } - return JSON.parse(json || '{}'); - } catch (e) { - console.warn('Error parsing collapse cookie', name, e); - return {}; - } - }; - - const writeCookieMap = (name, data) => { - try { - const serialized = encodeURIComponent(JSON.stringify(data || {})); - const maxAge = 60 * 60 * 24 * 365; // 1 year - document.cookie = `${name}=${serialized}; path=/; max-age=${maxAge}`; - } catch (e) { - console.warn('Error writing collapse cookie', name, e); - } - }; - - Users.getPublicCollapsedList = (boardId, listId) => { - if (!boardId || !listId) return null; - const data = readCookieMap('wekan-collapsed-lists'); - if (data[boardId] && typeof data[boardId][listId] === 'boolean') { - return data[boardId][listId]; - } - return null; - }; - - Users.setPublicCollapsedList = (boardId, listId, collapsed) => { - if (!boardId || !listId) return false; - const data = readCookieMap('wekan-collapsed-lists'); - if (!data[boardId]) data[boardId] = {}; - data[boardId][listId] = !!collapsed; - writeCookieMap('wekan-collapsed-lists', data); - return true; - }; - - Users.getPublicCollapsedSwimlane = (boardId, swimlaneId) => { - if (!boardId || !swimlaneId) return null; - const data = readCookieMap('wekan-collapsed-swimlanes'); - if (data[boardId] && typeof data[boardId][swimlaneId] === 'boolean') { - return data[boardId][swimlaneId]; - } - return null; - }; - - Users.setPublicCollapsedSwimlane = (boardId, swimlaneId, collapsed) => { - if (!boardId || !swimlaneId) return false; - const data = readCookieMap('wekan-collapsed-swimlanes'); - if (!data[boardId]) data[boardId] = {}; - data[boardId][swimlaneId] = !!collapsed; - writeCookieMap('wekan-collapsed-swimlanes', data); - return true; - }; - - Users.getPublicCardCollapsed = () => { - const data = readCookieMap('wekan-card-collapsed'); - return typeof data.state === 'boolean' ? data.state : null; - }; - - Users.setPublicCardCollapsed = collapsed => { - writeCookieMap('wekan-card-collapsed', { state: !!collapsed }); - return true; - }; -} - const allowedSortValues = [ '-modifiedAt', 'modifiedAt', @@ -250,13 +172,6 @@ Users.attachSchema( type: Boolean, optional: true, }, - 'profile.GreyIcons': { - /** - * per-user preference to render unicode icons in grey - */ - type: Boolean, - optional: true, - }, 'profile.cardMaximized': { /** * has user clicked maximize card? @@ -264,20 +179,6 @@ Users.attachSchema( type: Boolean, optional: true, }, - 'profile.cardCollapsed': { - /** - * has user collapsed the card details? - */ - type: Boolean, - optional: true, - }, - 'profile.showActivities': { - /** - * does the user want to show activities in card details? - */ - type: Boolean, - optional: true, - }, 'profile.customFieldsGrid': { /** * has user at card Custom Fields have Grid (false) or one per row (true) layout? @@ -299,29 +200,6 @@ Users.attachSchema( type: String, optional: true, }, - 'profile.boardWorkspacesTree': { - /** - * Per-user spaces tree for All Boards page - */ - type: Array, - optional: true, - }, - 'profile.boardWorkspacesTree.$': { - /** - * Space node: { id: String, name: String, children: Array<node> } - */ - type: Object, - blackbox: true, - optional: true, - }, - 'profile.boardWorkspaceAssignments': { - /** - * Per-user map of boardId -> spaceId - */ - type: Object, - optional: true, - blackbox: true, - }, 'profile.invitedBoards': { /** * board IDs the user has been invited to @@ -491,7 +369,6 @@ Users.attachSchema( 'board-view-swimlanes', 'board-view-lists', 'board-view-cal', - 'board-view-gantt', ], }, 'profile.listSortBy': { @@ -567,24 +444,6 @@ Users.attachSchema( defaultValue: {}, blackbox: true, }, - 'profile.collapsedLists': { - /** - * Per-user collapsed state for lists. - * profile[boardId][listId] = true|false - */ - type: Object, - defaultValue: {}, - blackbox: true, - }, - 'profile.collapsedSwimlanes': { - /** - * Per-user collapsed state for swimlanes. - * profile[boardId][swimlaneId] = true|false - */ - type: Object, - defaultValue: {}, - blackbox: true, - }, 'profile.keyboardShortcuts': { /** * User-specified state of keyboard shortcut activation. @@ -631,15 +490,6 @@ Users.attachSchema( type: Boolean, defaultValue: false, }, - 'profile.cardZoom': { - /** - * User-specified zoom level for card details (1.0 = 100%, 1.5 = 150%, etc.) - */ - type: Number, - defaultValue: 1.0, - min: 0.5, - max: 3.0, - }, services: { /** * services field of the user @@ -720,7 +570,7 @@ Users.attachSchema( ); // Security helpers for user updates -export const USER_UPDATE_ALLOWED_EXACT = ['username', 'profile', 'modifiedAt']; +export const USER_UPDATE_ALLOWED_EXACT = ['username']; export const USER_UPDATE_ALLOWED_PREFIXES = ['profile.']; export const USER_UPDATE_FORBIDDEN_PREFIXES = [ 'services', @@ -736,33 +586,24 @@ export const USER_UPDATE_FORBIDDEN_PREFIXES = [ ]; export function isUserUpdateAllowed(fields) { - const result = fields.every((f) => + return fields.every((f) => USER_UPDATE_ALLOWED_EXACT.includes(f) || USER_UPDATE_ALLOWED_PREFIXES.some((p) => f.startsWith(p)) ); - return result; } export function hasForbiddenUserUpdateField(fields) { - const result = fields.some((f) => USER_UPDATE_FORBIDDEN_PREFIXES.some((p) => f === p || f.startsWith(p + '.'))); - return result; + return fields.some((f) => USER_UPDATE_FORBIDDEN_PREFIXES.some((p) => f === p || f.startsWith(p + '.'))); } Users.allow({ update(userId, doc, fields /*, modifier */) { // Only the owner can update, and only for allowed fields - if (!userId || doc._id !== userId) { - return false; - } - if (!Array.isArray(fields) || fields.length === 0) { - return false; - } + if (!userId || doc._id !== userId) return false; + if (!Array.isArray(fields) || fields.length === 0) return false; // Disallow if any forbidden field present - if (hasForbiddenUserUpdateField(fields)) { - return false; - } + if (hasForbiddenUserUpdateField(fields)) return false; // Allow only username and profile.* - const allowed = isUserUpdateAllowed(fields); - return allowed; + return isUserUpdateAllowed(fields); }, remove(userId, doc) { // Disable direct client-side user removal for security @@ -776,8 +617,7 @@ Users.allow({ // Deny any attempts to touch forbidden fields from client updates Users.deny({ update(userId, doc, fields /*, modifier */) { - const denied = hasForbiddenUserUpdateField(fields); - return denied; + return hasForbiddenUserUpdateField(fields); }, fetch: [], }); @@ -844,7 +684,6 @@ Users.safeFields = { 'profile.initials': 1, 'profile.zoomLevel': 1, 'profile.mobileMode': 1, - 'profile.GreyIcons': 1, orgs: 1, teams: 1, authenticationMethod: 1, @@ -882,16 +721,6 @@ if (Meteor.isClient) { return board && board.hasCommentOnly(this._id); }, - isReadOnly() { - const board = Utils.getCurrentBoard(); - return board && board.hasReadOnly(this._id); - }, - - isReadAssignedOnly() { - const board = Utils.getCurrentBoard(); - return board && board.hasReadAssignedOnly(this._id); - }, - isNotWorker() { const board = Utils.getCurrentBoard(); return board && board.hasMember(this._id) && !board.hasWorker(this._id); @@ -1103,7 +932,7 @@ Users.helpers({ if (this._id) { return this.getSwimlaneHeight(boardId, swimlaneId); } - + // For non-logged-in users, get from localStorage try { const stored = localStorage.getItem('wekan-swimlane-heights'); @@ -1116,7 +945,7 @@ Users.helpers({ } catch (e) { console.warn('Error reading swimlane heights from localStorage:', e); } - + return -1; }, @@ -1125,17 +954,17 @@ Users.helpers({ if (this._id) { return this.setSwimlaneHeight(boardId, swimlaneId, height); } - + // For non-logged-in users, save to localStorage try { const stored = localStorage.getItem('wekan-swimlane-heights'); let heights = stored ? JSON.parse(stored) : {}; - + if (!heights[boardId]) { heights[boardId] = {}; } heights[boardId][swimlaneId] = height; - + localStorage.setItem('wekan-swimlane-heights', JSON.stringify(heights)); return true; } catch (e) { @@ -1208,11 +1037,6 @@ Users.helpers({ return profile.showDesktopDragHandles || false; }, - hasGreyIcons() { - const profile = this.profile || {}; - return profile.GreyIcons || false; - }, - hasCustomFieldsGrid() { const profile = this.profile || {}; return profile.customFieldsGrid || false; @@ -1223,11 +1047,6 @@ Users.helpers({ return profile.cardMaximized || false; }, - hasShowActivities() { - const profile = this.profile || {}; - return profile.showActivities || false; - }, - hasHiddenMinicardLabelText() { const profile = this.profile || {}; return profile.hiddenMinicardLabelText || false; @@ -1322,24 +1141,21 @@ Users.helpers({ if (this._id) { return this.getListWidth(boardId, listId); } - - // For non-logged-in users, get from validated localStorage - if (typeof localStorage !== 'undefined' && typeof getValidatedLocalStorageData === 'function') { - try { - const widths = getValidatedLocalStorageData('wekan-list-widths', validators.listWidths); + + // For non-logged-in users, get from localStorage + try { + const stored = localStorage.getItem('wekan-list-widths'); + if (stored) { + const widths = JSON.parse(stored); if (widths[boardId] && widths[boardId][listId]) { - const width = widths[boardId][listId]; - // Validate it's a valid number - if (validators.isValidNumber(width, 270, 1000)) { - return width; - } + return widths[boardId][listId]; } - } catch (e) { - console.warn('Error reading list widths from localStorage:', e); } + } catch (e) { + console.warn('Error reading list widths from localStorage:', e); } - - return 270; // Return default width + + return 270; // Return default width instead of -1 }, setListWidthToStorage(boardId, listId, width) { @@ -1347,30 +1163,23 @@ Users.helpers({ if (this._id) { return this.setListWidth(boardId, listId, width); } - - // Validate width before storing - if (!validators.isValidNumber(width, 270, 1000)) { - console.warn('Invalid list width:', width); + + // For non-logged-in users, save to localStorage + try { + const stored = localStorage.getItem('wekan-list-widths'); + let widths = stored ? JSON.parse(stored) : {}; + + if (!widths[boardId]) { + widths[boardId] = {}; + } + widths[boardId][listId] = width; + + localStorage.setItem('wekan-list-widths', JSON.stringify(widths)); + return true; + } catch (e) { + console.warn('Error saving list width to localStorage:', e); return false; } - - // For non-logged-in users, save to validated localStorage - if (typeof localStorage !== 'undefined' && typeof setValidatedLocalStorageData === 'function') { - try { - const widths = getValidatedLocalStorageData('wekan-list-widths', validators.listWidths); - - if (!widths[boardId]) { - widths[boardId] = {}; - } - widths[boardId][listId] = width; - - return setValidatedLocalStorageData('wekan-list-widths', widths, validators.listWidths); - } catch (e) { - console.warn('Error saving list width to localStorage:', e); - return false; - } - } - return false; }, getListConstraintFromStorage(boardId, listId) { @@ -1378,7 +1187,7 @@ Users.helpers({ if (this._id) { return this.getListConstraint(boardId, listId); } - + // For non-logged-in users, get from localStorage try { const stored = localStorage.getItem('wekan-list-constraints'); @@ -1391,7 +1200,7 @@ Users.helpers({ } catch (e) { console.warn('Error reading list constraints from localStorage:', e); } - + return 550; // Return default constraint instead of -1 }, @@ -1400,17 +1209,17 @@ Users.helpers({ if (this._id) { return this.setListConstraint(boardId, listId, constraint); } - + // For non-logged-in users, save to localStorage try { const stored = localStorage.getItem('wekan-list-constraints'); let constraints = stored ? JSON.parse(stored) : {}; - + if (!constraints[boardId]) { constraints[boardId] = {}; } constraints[boardId][listId] = constraint; - + localStorage.setItem('wekan-list-constraints', JSON.stringify(constraints)); return true; } catch (e) { @@ -1424,7 +1233,7 @@ Users.helpers({ if (this._id) { return this.getSwimlaneHeight(boardId, swimlaneId); } - + // For non-logged-in users, get from localStorage try { const stored = localStorage.getItem('wekan-swimlane-heights'); @@ -1437,7 +1246,7 @@ Users.helpers({ } catch (e) { console.warn('Error reading swimlane heights from localStorage:', e); } - + return -1; // Return -1 if not found }, @@ -1446,17 +1255,17 @@ Users.helpers({ if (this._id) { return this.setSwimlaneHeight(boardId, swimlaneId, height); } - + // For non-logged-in users, save to localStorage try { const stored = localStorage.getItem('wekan-swimlane-heights'); let heights = stored ? JSON.parse(stored) : {}; - + if (!heights[boardId]) { heights[boardId] = {}; } heights[boardId][swimlaneId] = height; - + localStorage.setItem('wekan-swimlane-heights', JSON.stringify(heights)); return true; } catch (e) { @@ -1464,341 +1273,330 @@ Users.helpers({ return false; } }, - // Per-user collapsed state helpers for lists/swimlanes - getCollapsedList(boardId, listId) { - const { collapsedLists = {} } = this.profile || {}; - if (collapsedLists[boardId] && typeof collapsedLists[boardId][listId] === 'boolean') { - return collapsedLists[boardId][listId]; - } - return null; - }, - getCollapsedSwimlane(boardId, swimlaneId) { - const { collapsedSwimlanes = {} } = this.profile || {}; - if (collapsedSwimlanes[boardId] && typeof collapsedSwimlanes[boardId][swimlaneId] === 'boolean') { - return collapsedSwimlanes[boardId][swimlaneId]; - } - return null; - }, - setCollapsedListToStorage(boardId, listId, collapsed) { - // Logged-in users: save to profile - if (this._id) { - return this.setCollapsedList(boardId, listId, collapsed); - } - // Public users: save to cookie - try { - const name = 'wekan-collapsed-lists'; - const stored = (typeof document !== 'undefined') ? document.cookie : ''; - const cookies = stored.split(';').map(c => c.trim()); - let json = '{}'; - for (const c of cookies) { - if (c.startsWith(name + '=')) { - json = decodeURIComponent(c.substring(name.length + 1)); - break; - } - } - let data = {}; - try { data = JSON.parse(json || '{}'); } catch (e) { data = {}; } - if (!data[boardId]) data[boardId] = {}; - data[boardId][listId] = !!collapsed; - const serialized = encodeURIComponent(JSON.stringify(data)); - const maxAge = 60 * 60 * 24 * 365; // 1 year - document.cookie = `${name}=${serialized}; path=/; max-age=${maxAge}`; - return true; - } catch (e) { - console.warn('Error saving collapsed list to cookie:', e); - return false; - } - }, - getCollapsedListFromStorage(boardId, listId) { - // Logged-in users: read from profile - if (this._id) { - const v = this.getCollapsedList(boardId, listId); - return v; - } - // Public users: read from cookie - try { - const name = 'wekan-collapsed-lists'; - const stored = (typeof document !== 'undefined') ? document.cookie : ''; - const cookies = stored.split(';').map(c => c.trim()); - let json = '{}'; - for (const c of cookies) { - if (c.startsWith(name + '=')) { - json = decodeURIComponent(c.substring(name.length + 1)); - break; - } - } - const data = JSON.parse(json || '{}'); - if (data[boardId] && typeof data[boardId][listId] === 'boolean') { - return data[boardId][listId]; - } - } catch (e) { - console.warn('Error reading collapsed list from cookie:', e); - } - return null; - }, - setCollapsedSwimlaneToStorage(boardId, swimlaneId, collapsed) { - // Logged-in users: save to profile - if (this._id) { - return this.setCollapsedSwimlane(boardId, swimlaneId, collapsed); - } - // Public users: save to cookie - try { - const name = 'wekan-collapsed-swimlanes'; - const stored = (typeof document !== 'undefined') ? document.cookie : ''; - const cookies = stored.split(';').map(c => c.trim()); - let json = '{}'; - for (const c of cookies) { - if (c.startsWith(name + '=')) { - json = decodeURIComponent(c.substring(name.length + 1)); - break; - } - } - let data = {}; - try { data = JSON.parse(json || '{}'); } catch (e) { data = {}; } - if (!data[boardId]) data[boardId] = {}; - data[boardId][swimlaneId] = !!collapsed; - const serialized = encodeURIComponent(JSON.stringify(data)); - const maxAge = 60 * 60 * 24 * 365; // 1 year - document.cookie = `${name}=${serialized}; path=/; max-age=${maxAge}`; - return true; - } catch (e) { - console.warn('Error saving collapsed swimlane to cookie:', e); - return false; - } - }, - getCollapsedSwimlaneFromStorage(boardId, swimlaneId) { - // Logged-in users: read from profile - if (this._id) { - const v = this.getCollapsedSwimlane(boardId, swimlaneId); - return v; - } - // Public users: read from cookie - try { - const name = 'wekan-collapsed-swimlanes'; - const stored = (typeof document !== 'undefined') ? document.cookie : ''; - const cookies = stored.split(';').map(c => c.trim()); - let json = '{}'; - for (const c of cookies) { - if (c.startsWith(name + '=')) { - json = decodeURIComponent(c.substring(name.length + 1)); - break; - } - } - const data = JSON.parse(json || '{}'); - if (data[boardId] && typeof data[boardId][swimlaneId] === 'boolean') { - return data[boardId][swimlaneId]; - } - } catch (e) { - console.warn('Error reading collapsed swimlane from cookie:', e); - } - return null; - }, +}); - async setMoveAndCopyDialogOption(boardId, options) { +Users.mutations({ + /** set the confirmed board id/swimlane id/list id of a board + * @param boardId the current board id + * @param options an object with the confirmed field values + */ + setMoveAndCopyDialogOption(boardId, options) { let currentOptions = this.getMoveAndCopyDialogOptions(); currentOptions[boardId] = options; - return await Users.updateAsync(this._id, { $set: { 'profile.moveAndCopyDialog': currentOptions } }); + return { + $set: { + 'profile.moveAndCopyDialog': currentOptions, + }, + }; }, - - async setMoveChecklistDialogOption(boardId, options) { + /** set the confirmed board id/swimlane id/list id/card id of a board (move checklist) + * @param boardId the current board id + * @param options an object with the confirmed field values + */ + setMoveChecklistDialogOption(boardId, options) { let currentOptions = this.getMoveChecklistDialogOptions(); currentOptions[boardId] = options; - return await Users.updateAsync(this._id, { $set: { 'profile.moveChecklistDialog': currentOptions } }); + return { + $set: { + 'profile.moveChecklistDialog': currentOptions, + }, + }; }, - - async setCopyChecklistDialogOption(boardId, options) { + /** set the confirmed board id/swimlane id/list id/card id of a board (copy checklist) + * @param boardId the current board id + * @param options an object with the confirmed field values + */ + setCopyChecklistDialogOption(boardId, options) { let currentOptions = this.getCopyChecklistDialogOptions(); currentOptions[boardId] = options; - return await Users.updateAsync(this._id, { $set: { 'profile.copyChecklistDialog': currentOptions } }); + return { + $set: { + 'profile.copyChecklistDialog': currentOptions, + }, + }; }, - - async toggleBoardStar(boardId) { + toggleBoardStar(boardId) { const queryKind = this.hasStarred(boardId) ? '$pull' : '$addToSet'; - return await Users.updateAsync(this._id, { [queryKind]: { 'profile.starredBoards': boardId } }); + return { + [queryKind]: { + 'profile.starredBoards': boardId, + }, + }; }, - - async setBoardSortIndex(boardId, sortIndex) { + /** + * Set per-user board sort index for a board + * Stored at profile.boardSortIndex[boardId] = sortIndex (Number) + */ + setBoardSortIndex(boardId, sortIndex) { const mapping = (this.profile && this.profile.boardSortIndex) || {}; mapping[boardId] = sortIndex; - return await Users.updateAsync(this._id, { $set: { 'profile.boardSortIndex': mapping } }); + return { + $set: { + 'profile.boardSortIndex': mapping, + }, + }; }, - - async toggleAutoWidth(boardId) { + toggleAutoWidth(boardId) { const { autoWidthBoards = {} } = this.profile || {}; autoWidthBoards[boardId] = !autoWidthBoards[boardId]; - return await Users.updateAsync(this._id, { $set: { 'profile.autoWidthBoards': autoWidthBoards } }); + return { + $set: { + 'profile.autoWidthBoards': autoWidthBoards, + }, + }; }, - - async toggleKeyboardShortcuts() { + toggleKeyboardShortcuts() { const { keyboardShortcuts = true } = this.profile || {}; - return await Users.updateAsync(this._id, { $set: { 'profile.keyboardShortcuts': !keyboardShortcuts } }); + return { + $set: { + 'profile.keyboardShortcuts': !keyboardShortcuts, + }, + }; }, - - async toggleVerticalScrollbars() { + toggleVerticalScrollbars() { const { verticalScrollbars = true } = this.profile || {}; - return await Users.updateAsync(this._id, { $set: { 'profile.verticalScrollbars': !verticalScrollbars } }); + return { + $set: { + 'profile.verticalScrollbars': !verticalScrollbars, + }, + }; }, - - async toggleShowWeekOfYear() { + toggleShowWeekOfYear() { const { showWeekOfYear = true } = this.profile || {}; - return await Users.updateAsync(this._id, { $set: { 'profile.showWeekOfYear': !showWeekOfYear } }); + return { + $set: { + 'profile.showWeekOfYear': !showWeekOfYear, + }, + }; }, - async addInvite(boardId) { - return await Users.updateAsync(this._id, { $addToSet: { 'profile.invitedBoards': boardId } }); + addInvite(boardId) { + return { + $addToSet: { + 'profile.invitedBoards': boardId, + }, + }; }, - async removeInvite(boardId) { - return await Users.updateAsync(this._id, { $pull: { 'profile.invitedBoards': boardId } }); + removeInvite(boardId) { + return { + $pull: { + 'profile.invitedBoards': boardId, + }, + }; }, - async addTag(tag) { - return await Users.updateAsync(this._id, { $addToSet: { 'profile.tags': tag } }); + addTag(tag) { + return { + $addToSet: { + 'profile.tags': tag, + }, + }; }, - async removeTag(tag) { - return await Users.updateAsync(this._id, { $pull: { 'profile.tags': tag } }); + removeTag(tag) { + return { + $pull: { + 'profile.tags': tag, + }, + }; }, - async toggleTag(tag) { - if (this.hasTag(tag)) { - return await this.removeTag(tag); - } else { - return await this.addTag(tag); - } + toggleTag(tag) { + if (this.hasTag(tag)) this.removeTag(tag); + else this.addTag(tag); }, - async setListSortBy(value) { - return await Users.updateAsync(this._id, { $set: { 'profile.listSortBy': value } }); + setListSortBy(value) { + return { + $set: { + 'profile.listSortBy': value, + }, + }; }, - async setName(value) { - return await Users.updateAsync(this._id, { $set: { 'profile.fullname': value } }); + setName(value) { + return { + $set: { + 'profile.fullname': value, + }, + }; }, - async toggleDesktopHandles(value = false) { - return await Users.updateAsync(this._id, { $set: { 'profile.showDesktopDragHandles': !value } }); + toggleDesktopHandles(value = false) { + return { + $set: { + 'profile.showDesktopDragHandles': !value, + }, + }; }, - async toggleFieldsGrid(value = false) { - return await Users.updateAsync(this._id, { $set: { 'profile.customFieldsGrid': !value } }); + toggleFieldsGrid(value = false) { + return { + $set: { + 'profile.customFieldsGrid': !value, + }, + }; }, - async toggleCardMaximized(value = false) { - return await Users.updateAsync(this._id, { $set: { 'profile.cardMaximized': !value } }); + toggleCardMaximized(value = false) { + return { + $set: { + 'profile.cardMaximized': !value, + }, + }; }, - async toggleCardCollapsed(value = false) { - return await Users.updateAsync(this._id, { $set: { 'profile.cardCollapsed': !value } }); + toggleLabelText(value = false) { + return { + $set: { + 'profile.hiddenMinicardLabelText': !value, + }, + }; + }, + toggleRescueCardDescription(value = false) { + return { + $set: { + 'profile.rescueCardDescription': !value, + }, + }; }, - async toggleShowActivities(value = false) { - return await Users.updateAsync(this._id, { $set: { 'profile.showActivities': !value } }); + addNotification(activityId) { + return { + $addToSet: { + 'profile.notifications': { + activity: activityId, + }, + }, + }; }, - async toggleLabelText(value = false) { - return await Users.updateAsync(this._id, { $set: { 'profile.hiddenMinicardLabelText': !value } }); + removeNotification(activityId) { + return { + $pull: { + 'profile.notifications': { + activity: activityId, + }, + }, + }; }, - async toggleRescueCardDescription(value = false) { - return await Users.updateAsync(this._id, { $set: { 'profile.rescueCardDescription': !value } }); + addEmailBuffer(text) { + return { + $addToSet: { + 'profile.emailBuffer': text, + }, + }; }, - async toggleGreyIcons(value = false) { - return await Users.updateAsync(this._id, { $set: { 'profile.GreyIcons': !value } }); + clearEmailBuffer() { + return { + $set: { + 'profile.emailBuffer': [], + }, + }; }, - async addNotification(activityId) { - return await Users.updateAsync(this._id, { - $addToSet: { 'profile.notifications': { activity: activityId, read: null } }, - }); + setAvatarUrl(avatarUrl) { + return { + $set: { + 'profile.avatarUrl': avatarUrl, + }, + }; }, - async removeNotification(activityId) { - return await Users.updateAsync(this._id, { - $pull: { 'profile.notifications': { activity: activityId } }, - }); + setShowCardsCountAt(limit) { + return { + $set: { + 'profile.showCardsCountAt': limit, + }, + }; }, - async addEmailBuffer(text) { - return await Users.updateAsync(this._id, { $addToSet: { 'profile.emailBuffer': text } }); + setStartDayOfWeek(startDay) { + return { + $set: { + 'profile.startDayOfWeek': startDay, + }, + }; }, - async clearEmailBuffer() { - return await Users.updateAsync(this._id, { $set: { 'profile.emailBuffer': [] } }); + setDateFormat(dateFormat) { + return { + $set: { + 'profile.dateFormat': dateFormat, + }, + }; }, - async setAvatarUrl(avatarUrl) { - return await Users.updateAsync(this._id, { $set: { 'profile.avatarUrl': avatarUrl } }); + setBoardView(view) { + return { + $set: { + 'profile.boardView': view, + }, + }; }, - async setShowCardsCountAt(limit) { - return await Users.updateAsync(this._id, { $set: { 'profile.showCardsCountAt': limit } }); - }, - - async setStartDayOfWeek(startDay) { - return await Users.updateAsync(this._id, { $set: { 'profile.startDayOfWeek': startDay } }); - }, - - async setDateFormat(dateFormat) { - return await Users.updateAsync(this._id, { $set: { 'profile.dateFormat': dateFormat } }); - }, - - async setBoardView(view) { - return await Users.updateAsync(this._id, { $set: { 'profile.boardView': view } }); - }, - - async setListWidth(boardId, listId, width) { + setListWidth(boardId, listId, width) { let currentWidths = this.getListWidths(); - if (!currentWidths[boardId]) currentWidths[boardId] = {}; + if (!currentWidths[boardId]) { + currentWidths[boardId] = {}; + } currentWidths[boardId][listId] = width; - return await Users.updateAsync(this._id, { $set: { 'profile.listWidths': currentWidths } }); + return { + $set: { + 'profile.listWidths': currentWidths, + }, + }; }, - async setListConstraint(boardId, listId, constraint) { + setListConstraint(boardId, listId, constraint) { let currentConstraints = this.getListConstraints(); - if (!currentConstraints[boardId]) currentConstraints[boardId] = {}; + if (!currentConstraints[boardId]) { + currentConstraints[boardId] = {}; + } currentConstraints[boardId][listId] = constraint; - return await Users.updateAsync(this._id, { $set: { 'profile.listConstraints': currentConstraints } }); + return { + $set: { + 'profile.listConstraints': currentConstraints, + }, + }; }, - async setSwimlaneHeight(boardId, swimlaneId, height) { + setSwimlaneHeight(boardId, swimlaneId, height) { let currentHeights = this.getSwimlaneHeights(); - if (!currentHeights[boardId]) currentHeights[boardId] = {}; + if (!currentHeights[boardId]) { + currentHeights[boardId] = {}; + } currentHeights[boardId][swimlaneId] = height; - return await Users.updateAsync(this._id, { $set: { 'profile.swimlaneHeights': currentHeights } }); + return { + $set: { + 'profile.swimlaneHeights': currentHeights, + }, + }; }, - async setCollapsedList(boardId, listId, collapsed) { - const current = (this.profile && this.profile.collapsedLists) || {}; - if (!current[boardId]) current[boardId] = {}; - current[boardId][listId] = !!collapsed; - return await Users.updateAsync(this._id, { $set: { 'profile.collapsedLists': current } }); + setZoomLevel(level) { + return { + $set: { + 'profile.zoomLevel': level, + }, + }; }, - async setCollapsedSwimlane(boardId, swimlaneId, collapsed) { - const current = (this.profile && this.profile.collapsedSwimlanes) || {}; - if (!current[boardId]) current[boardId] = {}; - current[boardId][swimlaneId] = !!collapsed; - return await Users.updateAsync(this._id, { $set: { 'profile.collapsedSwimlanes': current } }); - }, - - async setZoomLevel(level) { - return await Users.updateAsync(this._id, { $set: { 'profile.zoomLevel': level } }); - }, - - async setMobileMode(enabled) { - return await Users.updateAsync(this._id, { $set: { 'profile.mobileMode': enabled } }); - }, - - async setCardZoom(level) { - return await Users.updateAsync(this._id, { $set: { 'profile.cardZoom': level } }); + setMobileMode(enabled) { + return { + $set: { + 'profile.mobileMode': enabled, + }, + }; }, }); Meteor.methods({ // Secure user removal method with proper authorization checks - async removeUser(targetUserId) { + removeUser(targetUserId) { check(targetUserId, String); const currentUserId = Meteor.userId(); @@ -1806,12 +1604,12 @@ Meteor.methods({ throw new Meteor.Error('not-authorized', 'User must be logged in'); } - const currentUser = await ReactiveCache.getUser(currentUserId); + const currentUser = ReactiveCache.getUser(currentUserId); if (!currentUser) { throw new Meteor.Error('not-authorized', 'Current user not found'); } - const targetUser = await ReactiveCache.getUser(targetUserId); + const targetUser = ReactiveCache.getUser(targetUserId); if (!targetUser) { throw new Meteor.Error('user-not-found', 'Target user not found'); } @@ -1829,9 +1627,9 @@ Meteor.methods({ } // Check if target user is the last admin - const adminsNumber = (await ReactiveCache.getUsers({ + const adminsNumber = ReactiveCache.getUsers({ isAdmin: true, - })).length; + }).length; if (adminsNumber === 1 && targetUser.isAdmin) { throw new Meteor.Error('not-authorized', 'Cannot delete the last administrator'); @@ -1841,323 +1639,101 @@ Meteor.methods({ Users.remove(targetUserId); return { success: true, message: 'User deleted successfully' }; }, - async editUser(targetUserId, updateData) { - check(targetUserId, String); - check(updateData, Object); - - const currentUserId = Meteor.userId(); - if (!currentUserId) { - throw new Meteor.Error('not-authorized', 'User must be logged in'); - } - - const currentUser = await ReactiveCache.getUser(currentUserId); - if (!currentUser) { - throw new Meteor.Error('not-authorized', 'Current user not found'); - } - - // Check if current user is admin - if (!currentUser.isAdmin) { - throw new Meteor.Error('not-authorized', 'Only administrators can edit other users'); - } - - const targetUser = await ReactiveCache.getUser(targetUserId); - if (!targetUser) { - throw new Meteor.Error('user-not-found', 'Target user not found'); - } - - // Only allow updating specific fields - const updateObject = {}; - if (updateData.fullname !== undefined) { - updateObject['profile.fullname'] = updateData.fullname; - } - if (updateData.initials !== undefined) { - updateObject['profile.initials'] = updateData.initials; - } - if (updateData.isAdmin !== undefined) { - updateObject.isAdmin = updateData.isAdmin; - } - if (updateData.loginDisabled !== undefined) { - updateObject.loginDisabled = updateData.loginDisabled; - } - if (updateData.authenticationMethod !== undefined) { - updateObject.authenticationMethod = updateData.authenticationMethod; - } - if (updateData.importUsernames !== undefined) { - updateObject.importUsernames = updateData.importUsernames; - } - if (updateData.teams !== undefined) { - updateObject.teams = updateData.teams; - } - if (updateData.orgs !== undefined) { - updateObject.orgs = updateData.orgs; - } - - Users.update(targetUserId, { $set: updateObject }); - }, - async setListSortBy(value) { + setListSortBy(value) { check(value, String); - (await ReactiveCache.getCurrentUser()).setListSortBy(value); + ReactiveCache.getCurrentUser().setListSortBy(value); }, - setAvatarUrl(avatarUrl) { - check(avatarUrl, String); - if (!this.userId) { - throw new Meteor.Error('not-logged-in', 'User must be logged in'); - } - Users.update(this.userId, { $set: { 'profile.avatarUrl': avatarUrl } }); - }, - toggleBoardStar(boardId) { - check(boardId, String); - if (!this.userId) { - throw new Meteor.Error('not-logged-in', 'User must be logged in'); - } - const user = Users.findOne(this.userId); - if (!user) { - throw new Meteor.Error('user-not-found', 'User not found'); - } - - // Check if board is already starred - const starredBoards = (user.profile && user.profile.starredBoards) || []; - const isStarred = starredBoards.includes(boardId); - - // Build update object - const updateObject = isStarred - ? { $pull: { 'profile.starredBoards': boardId } } - : { $addToSet: { 'profile.starredBoards': boardId } }; - - Users.update(this.userId, updateObject); - }, - toggleGreyIcons(value) { - if (!this.userId) { - throw new Meteor.Error('not-logged-in', 'User must be logged in'); - } - if (value !== undefined) check(value, Boolean); - - const user = Users.findOne(this.userId); - if (!user) { - throw new Meteor.Error('user-not-found', 'User not found'); - } - - const current = (user.profile && user.profile.GreyIcons) || false; - const newValue = value !== undefined ? value : !current; - - Users.update(this.userId, { $set: { 'profile.GreyIcons': newValue } }); - return newValue; - }, - async toggleDesktopDragHandles() { - const user = await ReactiveCache.getCurrentUser(); + toggleDesktopDragHandles() { + const user = ReactiveCache.getCurrentUser(); user.toggleDesktopHandles(user.hasShowDesktopDragHandles()); }, - // Spaces: create a new space under parentId (or root when null) - createWorkspace(params) { - check(params, Object); - const { parentId = null, name } = params; - check(parentId, Match.OneOf(String, null)); - check(name, String); - if (!this.userId) throw new Meteor.Error('not-logged-in'); - const user = Users.findOne(this.userId) || {}; - const tree = (user.profile && user.profile.boardWorkspacesTree) ? EJSON.clone(user.profile.boardWorkspacesTree) : []; - - const newNode = { id: Random.id(), name, children: [] }; - - if (!parentId) { - tree.push(newNode); - } else { - const insertInto = (nodes) => { - for (let n of nodes) { - if (n.id === parentId) { - n.children = n.children || []; - n.children.push(newNode); - return true; - } - if (n.children && n.children.length) { - if (insertInto(n.children)) return true; - } - } - return false; - }; - insertInto(tree); - } - - Users.update(this.userId, { $set: { 'profile.boardWorkspacesTree': tree } }); - return newNode; - }, - // Spaces: set entire tree (used for drag-drop reordering) - setWorkspacesTree(newTree) { - check(newTree, Array); - if (!this.userId) throw new Meteor.Error('not-logged-in'); - Users.update(this.userId, { $set: { 'profile.boardWorkspacesTree': newTree } }); - return true; - }, - // Assign a board to a space - assignBoardToWorkspace(boardId, spaceId) { - check(boardId, String); - check(spaceId, String); - if (!this.userId) throw new Meteor.Error('not-logged-in'); - - const user = Users.findOne(this.userId, { fields: { 'profile.boardWorkspaceAssignments': 1 } }); - const assignments = user.profile?.boardWorkspaceAssignments || {}; - assignments[boardId] = spaceId; - - Users.update(this.userId, { - $set: { 'profile.boardWorkspaceAssignments': assignments } - }); - return true; - }, - // Remove a board assignment (moves it back to Remaining) - unassignBoardFromWorkspace(boardId) { - check(boardId, String); - if (!this.userId) throw new Meteor.Error('not-logged-in'); - - const user = Users.findOne(this.userId, { fields: { 'profile.boardWorkspaceAssignments': 1 } }); - const assignments = user.profile?.boardWorkspaceAssignments || {}; - delete assignments[boardId]; - - Users.update(this.userId, { - $set: { 'profile.boardWorkspaceAssignments': assignments } - }); - return true; - }, - async toggleHideCheckedItems() { - const user = await ReactiveCache.getCurrentUser(); + toggleHideCheckedItems() { + const user = ReactiveCache.getCurrentUser(); user.toggleHideCheckedItems(); }, - async toggleCustomFieldsGrid() { - const user = await ReactiveCache.getCurrentUser(); + toggleCustomFieldsGrid() { + const user = ReactiveCache.getCurrentUser(); user.toggleFieldsGrid(user.hasCustomFieldsGrid()); }, - async toggleCardMaximized() { - const user = await ReactiveCache.getCurrentUser(); + toggleCardMaximized() { + const user = ReactiveCache.getCurrentUser(); user.toggleCardMaximized(user.hasCardMaximized()); }, - setCardCollapsed(value) { - check(value, Boolean); - if (!this.userId) throw new Meteor.Error('not-logged-in'); - Users.update(this.userId, { $set: { 'profile.cardCollapsed': value } }); - }, - async toggleMinicardLabelText() { - const user = await ReactiveCache.getCurrentUser(); + toggleMinicardLabelText() { + const user = ReactiveCache.getCurrentUser(); user.toggleLabelText(user.hasHiddenMinicardLabelText()); }, - async toggleRescueCardDescription() { - const user = await ReactiveCache.getCurrentUser(); + toggleRescueCardDescription() { + const user = ReactiveCache.getCurrentUser(); user.toggleRescueCardDescription(user.hasRescuedCardDescription()); }, - async changeLimitToShowCardsCount(limit) { + changeLimitToShowCardsCount(limit) { check(limit, Number); - (await ReactiveCache.getCurrentUser()).setShowCardsCountAt(limit); + ReactiveCache.getCurrentUser().setShowCardsCountAt(limit); }, - async changeStartDayOfWeek(startDay) { + changeStartDayOfWeek(startDay) { check(startDay, Number); - (await ReactiveCache.getCurrentUser()).setStartDayOfWeek(startDay); + ReactiveCache.getCurrentUser().setStartDayOfWeek(startDay); }, - async changeDateFormat(dateFormat) { + changeDateFormat(dateFormat) { check(dateFormat, String); - (await ReactiveCache.getCurrentUser()).setDateFormat(dateFormat); + ReactiveCache.getCurrentUser().setDateFormat(dateFormat); }, - async applyListWidth(boardId, listId, width, constraint) { + applyListWidth(boardId, listId, width, constraint) { check(boardId, String); check(listId, String); check(width, Number); check(constraint, Number); - const user = await ReactiveCache.getCurrentUser(); + const user = ReactiveCache.getCurrentUser(); user.setListWidth(boardId, listId, width); user.setListConstraint(boardId, listId, constraint); }, - setListCollapsedState(boardId, listId, collapsed) { - check(boardId, String); - check(listId, String); - check(collapsed, Boolean); - if (!this.userId) { - throw new Meteor.Error('not-logged-in', 'User must be logged in'); - } - const user = Users.findOne(this.userId); - if (!user) { - throw new Meteor.Error('user-not-found', 'User not found'); - } - const current = (user.profile && user.profile.collapsedLists) || {}; - if (!current[boardId]) current[boardId] = {}; - current[boardId][listId] = !!collapsed; - Users.update(this.userId, { - $set: { - 'profile.collapsedLists': current, - }, - }); - }, - async applySwimlaneHeight(boardId, swimlaneId, height) { + applySwimlaneHeight(boardId, swimlaneId, height) { check(boardId, String); check(swimlaneId, String); check(height, Number); - const user = await ReactiveCache.getCurrentUser(); + const user = ReactiveCache.getCurrentUser(); user.setSwimlaneHeight(boardId, swimlaneId, height); }, - setSwimlaneCollapsedState(boardId, swimlaneId, collapsed) { - check(boardId, String); - check(swimlaneId, String); - check(collapsed, Boolean); - if (!this.userId) { - throw new Meteor.Error('not-logged-in', 'User must be logged in'); - } - const user = Users.findOne(this.userId); - if (!user) { - throw new Meteor.Error('user-not-found', 'User not found'); - } - const current = (user.profile && user.profile.collapsedSwimlanes) || {}; - if (!current[boardId]) current[boardId] = {}; - current[boardId][swimlaneId] = !!collapsed; - Users.update(this.userId, { - $set: { - 'profile.collapsedSwimlanes': current, - }, - }); - }, - - async applySwimlaneHeightToStorage(boardId, swimlaneId, height) { + applySwimlaneHeightToStorage(boardId, swimlaneId, height) { check(boardId, String); check(swimlaneId, String); check(height, Number); - const user = await ReactiveCache.getCurrentUser(); + const user = ReactiveCache.getCurrentUser(); if (user) { user.setSwimlaneHeightToStorage(boardId, swimlaneId, height); } // For non-logged-in users, the client-side code will handle localStorage }, - async applyListWidthToStorage(boardId, listId, width, constraint) { + applyListWidthToStorage(boardId, listId, width, constraint) { check(boardId, String); check(listId, String); check(width, Number); check(constraint, Number); - const user = await ReactiveCache.getCurrentUser(); + const user = ReactiveCache.getCurrentUser(); if (user) { user.setListWidthToStorage(boardId, listId, width); user.setListConstraintToStorage(boardId, listId, constraint); } // For non-logged-in users, the client-side code will handle localStorage }, - async setZoomLevel(level) { + setZoomLevel(level) { check(level, Number); - const user = await ReactiveCache.getCurrentUser(); + const user = ReactiveCache.getCurrentUser(); user.setZoomLevel(level); }, - async setMobileMode(enabled) { + setMobileMode(enabled) { check(enabled, Boolean); - const user = await ReactiveCache.getCurrentUser(); + const user = ReactiveCache.getCurrentUser(); user.setMobileMode(enabled); }, - async setBoardView(view) { - check(view, String); - const user = await ReactiveCache.getCurrentUser(); - if (!user) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - user.setBoardView(view); - }, }); if (Meteor.isServer) { Meteor.methods({ - async setCreateUser( + setCreateUser( fullname, username, initials, @@ -2187,13 +1763,13 @@ if (Meteor.isServer) { initials.includes('/')) { return false; } - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { - const nUsersWithUsername = (await ReactiveCache.getUsers({ + if (ReactiveCache.getCurrentUser()?.isAdmin) { + const nUsersWithUsername = ReactiveCache.getUsers({ username, - })).length; - const nUsersWithEmail = (await ReactiveCache.getUsers({ + }).length; + const nUsersWithEmail = ReactiveCache.getUsers({ email, - })).length; + }).length; if (nUsersWithUsername > 0) { throw new Meteor.Error('username-already-taken'); } else if (nUsersWithEmail > 0) { @@ -2208,8 +1784,8 @@ if (Meteor.isServer) { from: 'admin', }); const user = - await ReactiveCache.getUser(username) || - await ReactiveCache.getUser({ username }); + ReactiveCache.getUser(username) || + ReactiveCache.getUser({ username }); if (user) { Users.update(user._id, { $set: { @@ -2224,7 +1800,7 @@ if (Meteor.isServer) { } } }, - async setUsername(username, userId) { + setUsername(username, userId) { check(username, String); check(userId, String); // Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176 @@ -2233,10 +1809,10 @@ if (Meteor.isServer) { userId.includes('/')) { return false; } - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { - const nUsersWithUsername = (await ReactiveCache.getUsers({ + if (ReactiveCache.getCurrentUser()?.isAdmin) { + const nUsersWithUsername = ReactiveCache.getUsers({ username, - })).length; + }).length; if (nUsersWithUsername > 0) { throw new Meteor.Error('username-already-taken'); } else { @@ -2248,7 +1824,7 @@ if (Meteor.isServer) { } } }, - async setEmail(email, userId) { + setEmail(email, userId) { check(email, String); check(username, String); // Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176 @@ -2257,11 +1833,11 @@ if (Meteor.isServer) { email.includes('/')) { return false; } - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { if (Array.isArray(email)) { email = email.shift(); } - const existingUser = await ReactiveCache.getUser( + const existingUser = ReactiveCache.getUser( { 'emails.address': email, }, @@ -2287,7 +1863,7 @@ if (Meteor.isServer) { } } }, - async setUsernameAndEmail(username, email, userId) { + setUsernameAndEmail(username, email, userId) { check(username, String); check(email, String); check(userId, String); @@ -2298,22 +1874,22 @@ if (Meteor.isServer) { userId.includes('/')) { return false; } - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { if (Array.isArray(email)) { email = email.shift(); } - await Meteor.callAsync('setUsername', username, userId); - await Meteor.callAsync('setEmail', email, userId); + Meteor.call('setUsername', username, userId); + Meteor.call('setEmail', email, userId); } }, - async setPassword(newPassword, userId) { + setPassword(newPassword, userId) { check(userId, String); check(newPassword, String); - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { Accounts.setPassword(userId, newPassword); } }, - async setEmailVerified(email, verified, userId) { + setEmailVerified(email, verified, userId) { check(email, String); check(verified, Boolean); check(userId, String); @@ -2323,7 +1899,7 @@ if (Meteor.isServer) { userId.includes('/')) { return false; } - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { Users.update(userId, { $set: { emails: [ @@ -2336,7 +1912,7 @@ if (Meteor.isServer) { }); } }, - async setInitials(initials, userId) { + setInitials(initials, userId) { check(initials, String); check(userId, String); // Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176 @@ -2345,7 +1921,7 @@ if (Meteor.isServer) { userId.includes('/')) { return false; } - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { + if (ReactiveCache.getCurrentUser()?.isAdmin) { Users.update(userId, { $set: { 'profile.initials': initials, @@ -2354,7 +1930,7 @@ if (Meteor.isServer) { } }, // we accept userId, username, email - async inviteUserToBoard(username, boardId) { + inviteUserToBoard(username, boardId) { check(username, String); check(boardId, String); // Prevent Hyperlink Injection https://github.com/wekan/wekan/issues/5176 @@ -2363,11 +1939,16 @@ if (Meteor.isServer) { boardId.includes('/')) { return false; } - const inviter = await ReactiveCache.getCurrentUser(); - const board = await ReactiveCache.getBoard(boardId); - const member = _.find(board.members, function(member) { return member.userId === inviter._id; }); - if (!member) throw new Meteor.Error('error-board-notAMember'); - const allowInvite = member.isActive; + const inviter = ReactiveCache.getCurrentUser(); + const board = ReactiveCache.getBoard(boardId); + const allowInvite = + inviter && + board && + board.members && + _.contains(_.pluck(board.members, 'userId'), inviter._id) && + _.where(board.members, { + userId: inviter._id, + })[0].isActive; // GitHub issue 2060 //_.where(board.members, { userId: inviter._id })[0].isAdmin; if (!allowInvite) throw new Meteor.Error('error-board-notAMember'); @@ -2377,7 +1958,7 @@ if (Meteor.isServer) { const posAt = username.indexOf('@'); let user = null; if (posAt >= 0) { - user = await ReactiveCache.getUser({ + user = ReactiveCache.getUser({ emails: { $elemMatch: { address: username, @@ -2386,15 +1967,15 @@ if (Meteor.isServer) { }); } else { user = - await ReactiveCache.getUser(username) || - await ReactiveCache.getUser({ username }); + ReactiveCache.getUser(username) || + ReactiveCache.getUser({ username }); } if (user) { if (user._id === inviter._id) throw new Meteor.Error('error-user-notAllowSelf'); } else { if (posAt <= 0) throw new Meteor.Error('error-user-doesNotExist'); - if ((await ReactiveCache.getCurrentSetting()).disableRegistration) { + if (ReactiveCache.getCurrentSetting().disableRegistration) { throw new Meteor.Error('error-user-notCreated'); } // Set in lowercase email before creating account @@ -2420,29 +2001,19 @@ if (Meteor.isServer) { }); } Accounts.sendEnrollmentEmail(newUserId); - user = await ReactiveCache.getUser(newUserId); + user = ReactiveCache.getUser(newUserId); } - const memberIndex = board.members.findIndex(m => m.userId === user._id); - if (memberIndex >= 0) { - Boards.update(boardId, { $set: { [`members.${memberIndex}.isActive`]: true, modifiedAt: new Date() } }); - } else { - Boards.update(boardId, { $push: { members: { userId: user._id, isAdmin: false, isActive: true, isNoComments: false, isCommentOnly: false, isWorker: false, isNormalAssignedOnly: false, isCommentAssignedOnly: false, isReadOnly: false, isReadAssignedOnly: false } }, $set: { modifiedAt: new Date() } }); - } - Users.update(user._id, { $push: { 'profile.invitedBoards': boardId } }); + board.addMember(user._id); + user.addInvite(boardId); //Check if there is a subtasks board if (board.subtasksDefaultBoardId) { - const subBoard = await ReactiveCache.getBoard(board.subtasksDefaultBoardId); + const subBoard = ReactiveCache.getBoard(board.subtasksDefaultBoardId); //If there is, also add user to that board if (subBoard) { - const subMemberIndex = subBoard.members.findIndex(m => m.userId === user._id); - if (subMemberIndex >= 0) { - Boards.update(board.subtasksDefaultBoardId, { $set: { [`members.${subMemberIndex}.isActive`]: true, modifiedAt: new Date() } }); - } else { - Boards.update(board.subtasksDefaultBoardId, { $push: { members: { userId: user._id, isAdmin: false, isActive: true, isNoComments: false, isCommentOnly: false, isWorker: false, isNormalAssignedOnly: false, isCommentAssignedOnly: false, isReadOnly: false, isReadAssignedOnly: false } }, $set: { modifiedAt: new Date() } }); - } - Users.update(user._id, { $push: { 'profile.invitedBoards': subBoard._id } }); + subBoard.addMember(user._id); + user.addInvite(subBoard._id); } } try { const fullName = @@ -2497,35 +2068,35 @@ if (Meteor.isServer) { email: user.emails[0].address, }; }, - async impersonate(userId) { + impersonate(userId) { check(userId, String); - if (!(await ReactiveCache.getUser(userId))) + if (!ReactiveCache.getUser(userId)) throw new Meteor.Error(404, 'User not found'); - if (!(await ReactiveCache.getCurrentUser()).isAdmin) + if (!ReactiveCache.getCurrentUser().isAdmin) throw new Meteor.Error(403, 'Permission denied'); ImpersonatedUsers.insert({ - adminId: (await ReactiveCache.getCurrentUser())._id, + adminId: ReactiveCache.getCurrentUser()._id, userId: userId, reason: 'clickedImpersonate', }); this.setUserId(userId); }, - async isImpersonated(userId) { + isImpersonated(userId) { check(userId, String); - const isImpersonated = await ReactiveCache.getImpersonatedUser({ userId: userId }); + const isImpersonated = ReactiveCache.getImpersonatedUser({ userId: userId }); return isImpersonated; }, - async setUsersTeamsTeamDisplayName(teamId, teamDisplayName) { + setUsersTeamsTeamDisplayName(teamId, teamDisplayName) { check(teamId, String); check(teamDisplayName, String); - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { - for (const user of await ReactiveCache.getUsers({ + if (ReactiveCache.getCurrentUser()?.isAdmin) { + ReactiveCache.getUsers({ teams: { $elemMatch: { teamId: teamId }, }, - })) { + }).forEach((user) => { Users.update( { _id: user._id, @@ -2539,18 +2110,18 @@ if (Meteor.isServer) { }, }, ); - } + }); } }, - async setUsersOrgsOrgDisplayName(orgId, orgDisplayName) { + setUsersOrgsOrgDisplayName(orgId, orgDisplayName) { check(orgId, String); check(orgDisplayName, String); - if ((await ReactiveCache.getCurrentUser())?.isAdmin) { - for (const user of await ReactiveCache.getUsers({ + if (ReactiveCache.getCurrentUser()?.isAdmin) { + ReactiveCache.getUsers({ orgs: { $elemMatch: { orgId: orgId }, }, - })) { + }).forEach((user) => { Users.update( { _id: user._id, @@ -2564,12 +2135,12 @@ if (Meteor.isServer) { }, }, ); - } + }); } }, }); - Accounts.onCreateUser(async (options, user) => { - const userCount = (await ReactiveCache.getUsers({}, {}, true)).count(); + Accounts.onCreateUser((options, user) => { + const userCount = ReactiveCache.getUsers({}, {}, true).count(); user.isAdmin = userCount === 0; if (user.services.oidc) { @@ -2609,7 +2180,7 @@ if (Meteor.isServer) { user.authenticationMethod = 'oauth2'; // see if any existing user has this email address or username, otherwise create new - const existingUser = await ReactiveCache.getUser({ + const existingUser = ReactiveCache.getUser({ $or: [ { 'emails.address': email, @@ -2643,7 +2214,7 @@ if (Meteor.isServer) { return user; } - const disableRegistration = (await ReactiveCache.getCurrentSetting()).disableRegistration; + const disableRegistration = ReactiveCache.getCurrentSetting().disableRegistration; // If this is the first Authentication by the ldap and self registration disabled if (disableRegistration && options && options.ldap) { user.authenticationMethod = 'ldap'; @@ -2661,7 +2232,7 @@ if (Meteor.isServer) { 'The invitation code is required', ); } - const invitationCode = await ReactiveCache.getInvitationCode({ + const invitationCode = ReactiveCache.getInvitationCode({ code: options.profile.invitationcode, email: options.email, valid: true, @@ -2704,8 +2275,8 @@ const addCronJob = _.debounce( SyncedCron.add({ name: 'notification_cleanup', schedule: (parser) => parser.text('every 1 days'), - job: async () => { - for (const user of await ReactiveCache.getUsers()) { + job: () => { + for (const user of ReactiveCache.getUsers()) { if (!user.profile || !user.profile.notifications) continue; for (const notification of user.profile.notifications) { if (notification.read) { @@ -2727,11 +2298,11 @@ const addCronJob = _.debounce( if (Meteor.isServer) { // Let mongoDB ensure username unicity - Meteor.startup(async () => { - for (const value of allowedSortValues) { - await Lists._collection.createIndexAsync(value); - } - await Users._collection.createIndexAsync({ + Meteor.startup(() => { + allowedSortValues.forEach((value) => { + Lists._collection.createIndex(value); + }); + Users._collection.createIndex({ modifiedAt: -1, }); // Avatar URLs from CollectionFS to Meteor-Files, at users collection avatarUrl field: @@ -2940,9 +2511,9 @@ if (Meteor.isServer) { }); } - Users.after.insert(async (userId, doc) => { + Users.after.insert((userId, doc) => { // HACK - doc = await ReactiveCache.getUser(doc._id); + doc = ReactiveCache.getUser(doc._id); if (doc.createdThroughApi) { // The admin user should be able to create a user despite disabling registration because // it is two different things (registration and creation). @@ -2959,19 +2530,19 @@ if (Meteor.isServer) { } //invite user to corresponding boards - const disableRegistration = (await ReactiveCache.getCurrentSetting()).disableRegistration; + const disableRegistration = ReactiveCache.getCurrentSetting().disableRegistration; // If ldap, bypass the inviation code if the self registration isn't allowed. // TODO : pay attention if ldap field in the user model change to another content ex : ldap field to connection_type if (doc.authenticationMethod !== 'ldap' && disableRegistration) { let invitationCode = null; if (doc.authenticationMethod.toLowerCase() == 'oauth2') { // OIDC authentication mode - invitationCode = await ReactiveCache.getInvitationCode({ + invitationCode = ReactiveCache.getInvitationCode({ email: doc.emails[0].address.toLowerCase(), valid: true, }); } else { - invitationCode = await ReactiveCache.getInvitationCode({ + invitationCode = ReactiveCache.getInvitationCode({ code: doc.profile.icode, valid: true, }); @@ -2979,15 +2550,10 @@ if (Meteor.isServer) { if (!invitationCode) { throw new Meteor.Error('error-invitation-code-not-exist'); } else { - for (const boardId of invitationCode.boardsToBeInvited) { - const board = await ReactiveCache.getBoard(boardId); - const memberIndex = board.members.findIndex(m => m.userId === doc._id); - if (memberIndex >= 0) { - Boards.update(boardId, { $set: { [`members.${memberIndex}.isActive`]: true } }); - } else { - Boards.update(boardId, { $push: { members: { userId: doc._id, isAdmin: false, isActive: true, isNoComments: false, isCommentOnly: false, isWorker: false, isNormalAssignedOnly: false, isCommentAssignedOnly: false, isReadOnly: false, isReadAssignedOnly: false } } }); - } - } + invitationCode.boardsToBeInvited.forEach((boardId) => { + const board = ReactiveCache.getBoard(boardId); + board.addMember(doc._id); + }); if (!doc.profile) { doc.profile = {}; } @@ -3028,16 +2594,16 @@ if (Meteor.isServer) { * @summary returns the current user * @return_type Users */ - JsonRoutes.add('GET', '/api/user', async function (req, res) { + JsonRoutes.add('GET', '/api/user', function (req, res) { try { Authentication.checkLoggedIn(req.userId); - const data = await ReactiveCache.getUser({ + const data = ReactiveCache.getUser({ _id: req.userId, }); delete data.services; // get all boards where the user is member of - let boards = await ReactiveCache.getBoards( + let boards = ReactiveCache.getBoards( { type: 'board', 'members.userId': req.userId, @@ -3083,9 +2649,7 @@ if (Meteor.isServer) { Authentication.checkUserId(req.userId); JsonRoutes.sendResult(res, { code: 200, - data: Meteor.users.find({}, { - fields: { _id: 1, username: 1 } - }).map(function (doc) { + data: Meteor.users.find({}).map(function (doc) { return { _id: doc._id, username: doc.username, @@ -3110,22 +2674,22 @@ if (Meteor.isServer) { * @param {string} userId the user ID or username * @return_type Users */ - JsonRoutes.add('GET', '/api/users/:userId', async function (req, res) { + JsonRoutes.add('GET', '/api/users/:userId', function (req, res) { try { Authentication.checkUserId(req.userId); let id = req.params.userId; - let user = await ReactiveCache.getUser({ + let user = ReactiveCache.getUser({ _id: id, }); if (!user) { - user = await ReactiveCache.getUser({ + user = ReactiveCache.getUser({ username: id, }); id = user._id; } // get all boards where the user is member of - let boards = await ReactiveCache.getBoards( + let boards = ReactiveCache.getBoards( { type: 'board', 'members.userId': id, @@ -3174,17 +2738,17 @@ if (Meteor.isServer) { * @return_type {_id: string, * title: string} */ - JsonRoutes.add('PUT', '/api/users/:userId', async function (req, res) { + JsonRoutes.add('PUT', '/api/users/:userId', function (req, res) { try { Authentication.checkUserId(req.userId); const id = req.params.userId; const action = req.body.action; - let data = await ReactiveCache.getUser({ + let data = ReactiveCache.getUser({ _id: id, }); if (data !== undefined) { if (action === 'takeOwnership') { - const boards = await ReactiveCache.getBoards( + data = ReactiveCache.getBoards( { 'members.userId': id, 'members.isAdmin': true, @@ -3194,18 +2758,16 @@ if (Meteor.isServer) { sort: 1 /* boards default sorting */, }, }, - ); - data = []; - for (const board of boards) { + ).map(function (board) { if (board.hasMember(req.userId)) { - await board.removeMember(req.userId); + board.removeMember(req.userId); } board.changeOwnership(id, req.userId); - data.push({ + return { _id: board._id, title: board.title, - }); - } + }; + }); } else { if (action === 'disableLogin' && id !== req.userId) { Users.update( @@ -3231,7 +2793,7 @@ if (Meteor.isServer) { }, ); } - data = await ReactiveCache.getUser(id); + data = ReactiveCache.getUser(id); } } JsonRoutes.sendResult(res, { @@ -3264,56 +2826,39 @@ if (Meteor.isServer) { * @param {boolean} isNoComments disable comments * @param {boolean} isCommentOnly only enable comments * @param {boolean} isWorker is the user a board worker - * @param {boolean} isNormalAssignedOnly only see assigned cards (Normal permission) - * @param {boolean} isCommentAssignedOnly only comment on assigned cards - * @param {boolean} isReadOnly read-only access (no comments or editing) - * @param {boolean} isReadAssignedOnly read-only assigned cards only * @return_type {_id: string, * title: string} */ JsonRoutes.add( 'POST', '/api/boards/:boardId/members/:userId/add', - async function (req, res) { + function (req, res) { try { Authentication.checkUserId(req.userId); const userId = req.params.userId; const boardId = req.params.boardId; const action = req.body.action; - const { isAdmin, isNoComments, isCommentOnly, isWorker, isNormalAssignedOnly, isCommentAssignedOnly, isReadOnly, isReadAssignedOnly } = req.body; - let data = await ReactiveCache.getUser(userId); + const { isAdmin, isNoComments, isCommentOnly, isWorker } = req.body; + let data = ReactiveCache.getUser(userId); if (data !== undefined) { if (action === 'add') { - data = (await ReactiveCache.getBoards({ + data = ReactiveCache.getBoards({ _id: boardId, - })).map(function (board) { - const hasMember = board.members.some(m => m.userId === userId && m.isActive); - if (!hasMember) { - const memberIndex = board.members.findIndex(m => m.userId === userId); - if (memberIndex >= 0) { - Boards.update(boardId, { $set: { [`members.${memberIndex}.isActive`]: true } }); - } else { - Boards.update(boardId, { $push: { members: { userId: userId, isAdmin: false, isActive: true, isNoComments: false, isCommentOnly: false, isWorker: false, isNormalAssignedOnly: false, isCommentAssignedOnly: false, isReadOnly: false, isReadAssignedOnly: false } } }); - } + }).map(function (board) { + if (!board.hasMember(userId)) { + board.addMember(userId); function isTrue(data) { return data.toLowerCase() === 'true'; } - const memberIndex2 = board.members.findIndex(m => m.userId === userId); - if (memberIndex2 >= 0) { - Boards.update(boardId, { - $set: { - [`members.${memberIndex2}.isAdmin`]: isTrue(isAdmin), - [`members.${memberIndex2}.isNoComments`]: isTrue(isNoComments), - [`members.${memberIndex2}.isCommentOnly`]: isTrue(isCommentOnly), - [`members.${memberIndex2}.isWorker`]: isTrue(isWorker), - [`members.${memberIndex2}.isNormalAssignedOnly`]: isTrue(isNormalAssignedOnly), - [`members.${memberIndex2}.isCommentAssignedOnly`]: isTrue(isCommentAssignedOnly), - [`members.${memberIndex2}.isReadOnly`]: isTrue(isReadOnly), - [`members.${memberIndex2}.isReadAssignedOnly`]: isTrue(isReadAssignedOnly), - } - }); - } + board.setMemberPermission( + userId, + isTrue(isAdmin), + isTrue(isNoComments), + isTrue(isCommentOnly), + isTrue(isWorker), + userId, + ); } return { _id: board._id, @@ -3349,31 +2894,20 @@ if (Meteor.isServer) { JsonRoutes.add( 'POST', '/api/boards/:boardId/members/:userId/remove', - async function (req, res) { + function (req, res) { try { Authentication.checkUserId(req.userId); const userId = req.params.userId; const boardId = req.params.boardId; const action = req.body.action; - let data = await ReactiveCache.getUser(userId); + let data = ReactiveCache.getUser(userId); if (data !== undefined) { if (action === 'remove') { - data = (await ReactiveCache.getBoards({ + data = ReactiveCache.getBoards({ _id: boardId, - })).map(function (board) { - const hasMember = board.members.some(m => m.userId === userId && m.isActive); - if (hasMember) { - const memberIndex = board.members.findIndex(m => m.userId === userId); - if (memberIndex >= 0) { - const member = board.members[memberIndex]; - const activeAdmins = board.members.filter(m => m.isActive && m.isAdmin); - const allowRemove = !member.isAdmin || activeAdmins.length > 1; - if (!allowRemove) { - Boards.update(boardId, { $set: { [`members.${memberIndex}.isActive`]: true } }); - } else { - Boards.update(boardId, { $set: { [`members.${memberIndex}.isActive`]: false, [`members.${memberIndex}.isAdmin`]: false } }); - } - } + }).map(function (board) { + if (board.hasMember(userId)) { + board.removeMember(userId); } return { _id: board._id, @@ -3550,92 +3084,47 @@ if (Meteor.isServer) { }); // Server-side method to sanitize user data for search results - const sanitizeUserForSearch = (userData) => { - // Only allow safe fields for user search - const safeFields = { - _id: 1, - username: 1, - 'profile.fullname': 1, - 'profile.avatarUrl': 1, - 'profile.initials': 1, - 'emails.address': 1, - 'emails.verified': 1, - authenticationMethod: 1, - isAdmin: 1, - loginDisabled: 1, - teams: 1, - orgs: 1, - }; - - const sanitized = {}; - for (const field of Object.keys(safeFields)) { - if (userData[field] !== undefined) { - sanitized[field] = userData[field]; - } - } - - // Ensure sensitive fields are never included - delete sanitized.services; - delete sanitized.resume; - delete sanitized.email; - delete sanitized.createdAt; - delete sanitized.modifiedAt; - delete sanitized.sessionData; - delete sanitized.importUsernames; - - if (process.env.DEBUG === 'true') { - console.log('Sanitized user data for search:', Object.keys(sanitized)); - } - - return sanitized; - }; - Meteor.methods({ sanitizeUserForSearch(userData) { check(userData, Object); - return sanitizeUserForSearch(userData); - }, - async searchUsers(query, boardId) { - check(query, String); - check(boardId, String); - if (!this.userId) { - throw new Meteor.Error('not-logged-in', 'User must be logged in'); + // Only allow safe fields for user search + const safeFields = { + _id: 1, + username: 1, + 'profile.fullname': 1, + 'profile.avatarUrl': 1, + 'profile.initials': 1, + 'emails.address': 1, + 'emails.verified': 1, + authenticationMethod: 1, + isAdmin: 1, + loginDisabled: 1, + teams: 1, + orgs: 1, + }; + + const sanitized = {}; + for (const field of Object.keys(safeFields)) { + if (userData[field] !== undefined) { + sanitized[field] = userData[field]; + } } - const currentUser = await ReactiveCache.getCurrentUser(); - const board = await ReactiveCache.getBoard(boardId); + // Ensure sensitive fields are never included + delete sanitized.services; + delete sanitized.resume; + delete sanitized.email; + delete sanitized.createdAt; + delete sanitized.modifiedAt; + delete sanitized.sessionData; + delete sanitized.importUsernames; - // Check if current user is a member of the board - const member = _.find(board.members, function(member) { return member.userId === currentUser._id; }); - if (!member || !member.isActive) { - throw new Meteor.Error('not-authorized', 'User is not a member of this board'); + if (process.env.DEBUG === 'true') { + console.log('Sanitized user data for search:', Object.keys(sanitized)); } - if (query.length < 2) { - return []; - } - - const searchRegex = new RegExp(query, 'i'); - const users = await ReactiveCache.getUsers({ - $or: [ - { username: searchRegex }, - { 'profile.fullname': searchRegex }, - { 'emails.address': searchRegex } - ] - }, { - fields: { - _id: 1, - username: 1, - 'profile.fullname': 1, - 'profile.avatarUrl': 1, - 'profile.initials': 1, - 'emails.address': 1 - }, - limit: 5 - }); - - return users.map(user => sanitizeUserForSearch(user)); + return sanitized; } }); } diff --git a/models/usersessiondata.js b/models/usersessiondata.js index 04be3748e..a100574e6 100644 --- a/models/usersessiondata.js +++ b/models/usersessiondata.js @@ -1,5 +1,3 @@ -import { incrementCounter } from './counters'; - SessionData = new Mongo.Collection('sessiondata'); /** @@ -16,7 +14,7 @@ SessionData.attachSchema( // eslint-disable-next-line consistent-return autoValue() { if (this.isInsert && !this.isSet) { - return incrementCounter('orgId', 1); + return incrementCounter('counters', 'orgId', 1); } }, }, diff --git a/models/watchable.js b/models/watchable.js index 73cb0564c..7dbacb594 100644 --- a/models/watchable.js +++ b/models/watchable.js @@ -19,13 +19,13 @@ const simpleWatchable = collection => { findWatcher(userId) { return _.contains(this.watchers, userId); }, + }); - async setWatcher(userId, level) { + collection.mutations({ + setWatcher(userId, level) { // if level undefined or null or false, then remove - if (!level) { - return await collection.updateAsync(this._id, { $pull: { watchers: userId } }); - } - return await collection.updateAsync(this._id, { $addToSet: { watchers: userId } }); + if (!level) return { $pull: { watchers: userId } }; + return { $addToSet: { watchers: userId } }; }, }); }; @@ -66,20 +66,20 @@ const complexWatchable = collection => { const watcher = this.findWatcher(userId); return watcher ? watcher.level : complexWatchDefault; }, + }); - async setWatcher(userId, level) { + collection.mutations({ + setWatcher(userId, level) { // if level undefined or null or false, then remove if (level === complexWatchDefault) level = null; - if (!level) { - return await collection.updateAsync(this._id, { $pull: { watchers: { userId } } }); - } + if (!level) return { $pull: { watchers: { userId } } }; const index = this.watcherIndex(userId); - if (index < 0) { - return await collection.updateAsync(this._id, { $push: { watchers: { userId, level } } }); - } - return await collection.updateAsync(this._id, { - $set: { [`watchers.${index}.level`]: level }, - }); + if (index < 0) return { $push: { watchers: { userId, level } } }; + return { + $set: { + [`watchers.${index}.level`]: level, + }, + }; }, }); }; diff --git a/models/wekanCreator.js b/models/wekanCreator.js index 3d61338d1..4d7f4425d 100644 --- a/models/wekanCreator.js +++ b/models/wekanCreator.js @@ -1,27 +1,24 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import { CustomFields } from './customFields'; -import { - formatDateTime, - formatDate, - formatTime, - getISOWeek, - isValidDate, - isBefore, - isAfter, - isSame, - add, - subtract, - startOf, - endOf, - format, - parseDate, - now, - createDate, - fromNow, - calendar +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar } from '/imports/lib/dateUtils'; -import getSlug from 'limax'; -import { validateAttachmentUrl } from './lib/attachmentUrlValidation'; const DateString = Match.Where(function(dateAsString) { check(dateAsString, String); @@ -79,33 +76,6 @@ export class WekanCreator { // maps a wekanCardId to an array of wekanAttachments this.attachments = {}; - - // default swimlane id created during import if necessary - this._defaultSwimlaneId = null; - - // Normalize possible exported id fields: some exports may use `id` instead of `_id`. - // Ensure every item we rely on has an `_id` so mappings work consistently. - const normalizeIds = arr => { - if (!arr) return; - arr.forEach(item => { - if (item && item.id && !item._id) { - item._id = item.id; - } - }); - }; - - normalizeIds(data.lists); - normalizeIds(data.cards); - normalizeIds(data.swimlanes); - normalizeIds(data.checklists); - normalizeIds(data.checklistItems); - normalizeIds(data.triggers); - normalizeIds(data.actions); - normalizeIds(data.labels); - normalizeIds(data.customFields); - normalizeIds(data.comments); - normalizeIds(data.activities); - normalizeIds(data.rules); } /** @@ -245,14 +215,14 @@ export class WekanCreator { ]); } - async getMembersToMap(data) { + getMembersToMap(data) { // we will work on the list itself (an ordered array of objects) when a // mapping is done, we add a 'wekan' field to the object representing the // imported member const membersToMap = data.members; const users = data.users; // auto-map based on username - for (const importedMember of membersToMap) { + membersToMap.forEach(importedMember => { importedMember.id = importedMember.userId; delete importedMember.userId; const user = users.filter(user => { @@ -262,11 +232,11 @@ export class WekanCreator { importedMember.fullName = user.profile.fullname; } importedMember.username = user.username; - const wekanUser = await ReactiveCache.getUser({ username: importedMember.username }); + const wekanUser = ReactiveCache.getUser({ username: importedMember.username }); if (wekanUser) { importedMember.wekanId = wekanUser._id; } - } + }); return membersToMap; } @@ -281,7 +251,7 @@ export class WekanCreator { } // You must call parseActions before calling this one. - async createBoardAndLabels(boardToImport) { + createBoardAndLabels(boardToImport) { const boardToCreate = { archived: boardToImport.archived, color: boardToImport.color, @@ -305,7 +275,7 @@ export class WekanCreator { permission: boardToImport.permission, slug: getSlug(boardToImport.title) || 'board', stars: 0, - title: await Boards.uniqueTitle(boardToImport.title), + title: Boards.uniqueTitle(boardToImport.title), }; // now add other members if (boardToImport.members) { @@ -378,7 +348,7 @@ export class WekanCreator { dateLastActivity: this._now(), description: card.description, listId: this.lists[card.listId], - swimlaneId: this.swimlanes[card.swimlaneId] || this._defaultSwimlaneId, + swimlaneId: this.swimlanes[card.swimlaneId], sort: card.sort, title: card.title, // we attribute the card to its creator if available @@ -520,17 +490,6 @@ export class WekanCreator { } }; if (att.url) { - const validation = validateAttachmentUrl(att.url); - if (!validation.valid) { - if (process.env.DEBUG === 'true') { - console.warn( - 'Blocked attachment URL during Wekan import:', - validation.reason, - att.url, - ); - } - return; - } Attachments.load(att.url, opts, cb, true); } else if (att.file) { Attachments.insert(att.file, opts, cb, true); @@ -629,25 +588,6 @@ export class WekanCreator { } createSwimlanes(wekanSwimlanes, boardId) { - // If no swimlanes provided, create a default so cards still render - if (!wekanSwimlanes || wekanSwimlanes.length === 0) { - const swimlaneToCreate = { - archived: false, - boardId, - createdAt: this._now(), - title: 'Default', - sort: 0, - }; - const created = Swimlanes.direct.insert(swimlaneToCreate); - Swimlanes.direct.update(created, { - $set: { - updatedAt: this._now(), - }, - }); - this._defaultSwimlaneId = created; - return; - } - wekanSwimlanes.forEach((swimlane, swimlaneIndex) => { const swimlaneToCreate = { archived: swimlane.archived, @@ -671,14 +611,11 @@ export class WekanCreator { }, }); this.swimlanes[swimlane._id] = swimlaneId; - if (!this._defaultSwimlaneId) { - this._defaultSwimlaneId = swimlaneId; - } }); } - async createSubtasks(wekanCards) { - for (const card of wekanCards) { + createSubtasks(wekanCards) { + wekanCards.forEach(card => { // get new id of card (in created / new board) const cardIdInNewBoard = this.cards[card._id]; @@ -695,7 +632,7 @@ export class WekanCreator { : card.parentId; //if the parent card exists, proceed - if (await ReactiveCache.getCard(parentIdInNewBoard)) { + if (ReactiveCache.getCard(parentIdInNewBoard)) { //set parent id of the card in the new board to the new id of the parent Cards.direct.update(cardIdInNewBoard, { $set: { @@ -703,7 +640,7 @@ export class WekanCreator { }, }); } - } + }); } createChecklists(wekanChecklists) { @@ -983,23 +920,23 @@ export class WekanCreator { // } } - async create(board, currentBoardId) { + create(board, currentBoardId) { // TODO : Make isSandstorm variable global const isSandstorm = Meteor.settings && Meteor.settings.public && Meteor.settings.public.sandstorm; if (isSandstorm && currentBoardId) { - const currentBoard = await ReactiveCache.getBoard(currentBoardId); - await currentBoard.archive(); + const currentBoard = ReactiveCache.getBoard(currentBoardId); + currentBoard.archive(); } this.parseActivities(board); - const boardId = await this.createBoardAndLabels(board); + const boardId = this.createBoardAndLabels(board); this.createLists(board.lists, boardId); this.createSwimlanes(board.swimlanes, boardId); this.createCustomFields(board.customFields, boardId); this.createCards(board.cards, boardId); - await this.createSubtasks(board.cards); + this.createSubtasks(board.cards); this.createChecklists(board.checklists); this.createChecklistItems(board.checklistItems); this.importActivities(board.activities, boardId); diff --git a/models/wekanmapper.js b/models/wekanmapper.js index be849af7c..3dc449c7a 100644 --- a/models/wekanmapper.js +++ b/models/wekanmapper.js @@ -1,13 +1,13 @@ import { ReactiveCache } from '/imports/reactiveCache'; -export async function getMembersToMap(data) { +export function getMembersToMap(data) { // we will work on the list itself (an ordered array of objects) when a // mapping is done, we add a 'wekan' field to the object representing the // imported member const membersToMap = data.members; const users = data.users; // auto-map based on username - for (const importedMember of membersToMap) { + membersToMap.forEach(importedMember => { importedMember.id = importedMember.userId; delete importedMember.userId; const user = users.filter(user => { @@ -17,10 +17,10 @@ export async function getMembersToMap(data) { importedMember.fullName = user.profile.fullname; } importedMember.username = user.username; - const wekanUser = await ReactiveCache.getUser({ username: importedMember.username }); + const wekanUser = ReactiveCache.getUser({ username: importedMember.username }); if (wekanUser) { importedMember.wekanId = wekanUser._id; } - } + }); return membersToMap; } diff --git a/package-lock.json b/package-lock.json index a3029faa5..9e338c147 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,18 +1,13 @@ { "name": "wekan", - "version": "v8.35.0", + "version": "v8.16.0", "lockfileVersion": 1, "requires": true, "dependencies": { "@babel/runtime": { - "version": "7.28.6", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.6.tgz", - "integrity": "sha512-05WQkdpL9COIMz4LjTxGpPNCdlpyimKppYNoJ5Di5EUObifl8t4tuLuUBBZEpoLYOmfvIWrsp9fCl0HoPRVTdA==" - }, - "@borewit/text-codec": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@borewit/text-codec/-/text-codec-0.2.1.tgz", - "integrity": "sha512-k7vvKPbf7J2fZ5klGRD9AeKfUvojuZIQ3BT5u7Jfv+puwXkUBUT5PVyMDfJZpy30CBDXGMgw7fguK/lpOMBvgw==" + "version": "7.28.4", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.28.4.tgz", + "integrity": "sha512-Q/N6JNWvIvPnLDvjlE1OUBLPQHH6l3CltCEsHIujp45zQUSSh8K+gHnaEX45yAT1nyngnINhvWtzN+Nb9D8RAQ==" }, "@fast-csv/format": { "version": "4.3.5", @@ -41,31 +36,25 @@ "lodash.uniq": "^4.5.0" } }, - "@isaacs/fs-minipass": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@isaacs/fs-minipass/-/fs-minipass-4.0.1.tgz", - "integrity": "sha512-wgm9Ehl2jpeqP3zw/7mo3kRHFp5MEDhqAdwy1fTGkHAwnkGOVsgpvQhL8B5n1qlb01jV3n/bI0ZfZp5lWA1k4w==", - "requires": { - "minipass": "^7.0.4" - } - }, "@kurkle/color": { "version": "0.3.4", "resolved": "https://registry.npmjs.org/@kurkle/color/-/color-0.3.4.tgz", "integrity": "sha512-M5UknZPHRu3DEDWoipU6sE8PdkZ6Z/S+v4dD+Ke8IaNlpdSQah50lz1KtcFBa2vsdOnwbbnxJwVM4wty6udA5w==" }, "@mapbox/node-pre-gyp": { - "version": "2.0.3", - "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-2.0.3.tgz", - "integrity": "sha512-uwPAhccfFJlsfCxMYTwOdVfOz3xqyj8xYL3zJj8f0pb30tLohnnFPhLuqp4/qoEz8sNxe4SESZedcBojRefIzg==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz", + "integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==", "requires": { - "consola": "^3.2.3", "detect-libc": "^2.0.0", - "https-proxy-agent": "^7.0.5", + "https-proxy-agent": "^5.0.0", + "make-dir": "^3.1.0", "node-fetch": "^2.6.7", - "nopt": "^8.0.0", - "semver": "^7.5.3", - "tar": "^7.4.0" + "nopt": "^5.0.0", + "npmlog": "^5.0.1", + "rimraf": "^3.0.2", + "semver": "^7.3.5", + "tar": "^6.1.11" } }, "@meteorjs/reify": { @@ -80,88 +69,47 @@ } }, "@rwap/jquery-ui-touch-punch": { - "version": "1.1.5", - "resolved": "https://registry.npmjs.org/@rwap/jquery-ui-touch-punch/-/jquery-ui-touch-punch-1.1.5.tgz", - "integrity": "sha512-2rGIFBjqjo/U8RWKLAIi4hhZLBy64KReoWjlgHRSKO6BGXDKis+aD1wwcM67KhavJsV3pYaXSK0XlDRw3apmag==", + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@rwap/jquery-ui-touch-punch/-/jquery-ui-touch-punch-1.0.11.tgz", + "integrity": "sha512-GFRfHxnl9uH4v4F7E+rtXYbcsDYSas5fqfmKWVy/dfs+BDfLUqClIOL5MZlroDFoL3T9bCbsFMFXULHSL34ZdA==", "requires": { "jquery-ui": ">=1.8" } }, "@sinonjs/commons": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.1.tgz", - "integrity": "sha512-K3mCHKQ9sVh8o1C9cxkwxaOmXoAMlDxC1mYyHrjqOWEcBjYr76t96zL2zlj5dUGZ3HSw240X1qgH3Mjf1yJWpQ==", + "version": "1.8.6", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-1.8.6.tgz", + "integrity": "sha512-Ky+XkAkqPZSm3NLBeUng77EBQl3cmeJhITaGHdYH8kjVB+aun3S4XBRti2zt17mtt0mIUDiNxYeoJm6drVvBJQ==", "dev": true, "requires": { "type-detect": "4.0.8" } }, "@sinonjs/fake-timers": { - "version": "15.1.0", - "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-15.1.0.tgz", - "integrity": "sha512-cqfapCxwTGsrR80FEgOoPsTonoefMBY7dnUEbQ+GRcved0jvkJLzvX6F4WtN+HBqbPX/SiFsIRUp+IrCW/2I2w==", + "version": "9.1.2", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-9.1.2.tgz", + "integrity": "sha512-BPS4ynJW/o92PUR4wgriz2Ud5gpST5vz6GQfMixEDK0Z8ZCUv2M7SkBLykH56T++Xs+8ln9zTGbOvNGIe02/jw==", "dev": true, "requires": { - "@sinonjs/commons": "^3.0.1" + "@sinonjs/commons": "^1.7.0" } }, "@sinonjs/samsam": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-8.0.3.tgz", - "integrity": "sha512-hw6HbX+GyVZzmaYNh82Ecj1vdGZrqVIn/keDTg63IgAwiQPO+xCz99uG6Woqgb4tM0mUiFENKZ4cqd7IX94AXQ==", + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/@sinonjs/samsam/-/samsam-6.1.3.tgz", + "integrity": "sha512-nhOb2dWPeb1sd3IQXL/dVPnKHDOAFfvichtBf4xV00/rU1QbPCQqKMbvIheIjqwVjh7qIgf2AHTHi391yMOMpQ==", "dev": true, "requires": { - "@sinonjs/commons": "^3.0.1", - "type-detect": "^4.1.0" - }, - "dependencies": { - "type-detect": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", - "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", - "dev": true - } + "@sinonjs/commons": "^1.6.0", + "lodash.get": "^4.4.2", + "type-detect": "^4.0.8" } }, - "@textcomplete/contenteditable": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@textcomplete/contenteditable/-/contenteditable-0.1.13.tgz", - "integrity": "sha512-O2BNqtvP0I1lL8WIwJ/ilCVi6rEJu2Jtj7Nnx8+XSN66aoBV5pdl0c1IXFfNvGU5kJh+6EOxkDEmm2NhYCIXlw==", - "requires": { - "@textcomplete/utils": "^0.1.13" - } - }, - "@textcomplete/core": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@textcomplete/core/-/core-0.1.13.tgz", - "integrity": "sha512-C4S+ihQU5HsKQ/TbsmS0e7hfPZtLZbEXj5NDUgRnhu/1Nezpu892bjNZGeErZm+R8iyDIT6wDu6EgIhng4M8eQ==", - "requires": { - "eventemitter3": "^5.0.1" - } - }, - "@textcomplete/textarea": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@textcomplete/textarea/-/textarea-0.1.13.tgz", - "integrity": "sha512-GNathnXpV361YuZrBVXvVqFYZ5NQZsjGC7Bt2sCUA/RTWlIgxHxC0ruDChYyRDx4siQZiZZOO5pWz+z1x8pZFQ==", - "requires": { - "@textcomplete/utils": "^0.1.13", - "textarea-caret": "^3.1.0", - "undate": "^0.3.0" - } - }, - "@textcomplete/utils": { - "version": "0.1.13", - "resolved": "https://registry.npmjs.org/@textcomplete/utils/-/utils-0.1.13.tgz", - "integrity": "sha512-5UW9Ee0WEX1s9K8MFffo5sfUjYm3YVhtqRhAor/ih7p0tnnpaMB7AwMRDKwhSIQL6O+g1fmEkxCeO8WqjPzjUA==" - }, - "@tokenizer/inflate": { - "version": "0.4.1", - "resolved": "https://registry.npmjs.org/@tokenizer/inflate/-/inflate-0.4.1.tgz", - "integrity": "sha512-2mAv+8pkG6GIZiF1kNg1jAjh27IDxEPKwdGul3snfztFerfPGI1LjDezZp3i7BElXompqEtPmoPx6c2wgtWsOA==", - "requires": { - "debug": "^4.4.3", - "token-types": "^6.1.1" - } + "@sinonjs/text-encoding": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/@sinonjs/text-encoding/-/text-encoding-0.7.2.tgz", + "integrity": "sha512-sXXKG+uL9IrKqViTtao2Ws6dy0znu9sOaP1di/jKGW1M6VssO8vlpXCQcpZ+jisQ1tTFAC5Jo/EOzFbggBagFQ==", + "dev": true }, "@tokenizer/token": { "version": "0.3.0", @@ -185,17 +133,15 @@ "optional": true }, "@wekanteam/dragscroll": { - "version": "0.0.9", - "resolved": "https://registry.npmjs.org/@wekanteam/dragscroll/-/dragscroll-0.0.9.tgz", - "integrity": "sha512-DAz2ZDtUn7dd0Zol1wdKkhSG4U+OwlDcGzeu1t8XwWh9SKtfTaIaMYTqTvJfAg2B3ilIHp2k64c5mqOiRq5lWQ==" + "version": "git+https://github.com/wekan/dragscroll.git#6ea215c8cdbde9362ecba8ffb72ce9f9fde842d2", + "from": "git+https://github.com/wekan/dragscroll.git" }, "@wekanteam/exceljs": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/@wekanteam/exceljs/-/exceljs-4.5.1.tgz", - "integrity": "sha512-qEWJKSjExu7YJ07YSp3BVj8UvVz1hQR7yh18XdxOn7Wu41wXjbcFpXuMr8GNtj11mE33z5xdUyADcrKLJVfVLQ==", + "version": "git+https://github.com/wekan/exceljs.git#e0229907e7a81bc3fe6daf4e42b1fdfbecdcb7cb", + "from": "git+https://github.com/wekan/exceljs.git", "requires": { "archiver": "^5.0.0", - "dayjs": "^1.11.19", + "dayjs": "^1.8.34", "fast-csv": "^4.3.1", "jszip": "^3.10.1", "readable-stream": "^3.6.0", @@ -211,30 +157,33 @@ "integrity": "sha512-PxeGIu/HMjmL84N2Dj5qp4lFlBP4jV/y6WU/JhDiFPx6gfGEWXgDcc9sShTPNvECtToGAA0SCD6T/k50CMHi8Q==" }, "@wekanteam/meteor-globals": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/@wekanteam/meteor-globals/-/meteor-globals-1.1.6.tgz", - "integrity": "sha512-JJJZLXbvdpfRYtBBQ9JEk0iLU6yCs076r+qJ4e8gMD/AQpva7xOeLzMPdmzfemQ0M/Asa+7mVS1tU+2/5VQO9w==", + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/@wekanteam/meteor-globals/-/meteor-globals-1.1.4.tgz", + "integrity": "sha512-zaq+/F+5/aI46JXXcp3LhcYrM+ZQ0aH99BKuFyP0Ie1ACnYPqHqhUwCwScGiauxmMc9abHduC6DJTbxnJGc2QQ==", "requires": { "semver": "^7.5.4" } }, "@wekanteam/meteor-reactive-cache": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@wekanteam/meteor-reactive-cache/-/meteor-reactive-cache-1.0.8.tgz", - "integrity": "sha512-Vlv24M4TYSK8NBEixdtdgstijmRIPPyJMfwDq0sUYtTLjSjQXW/RpmSwRq/kwVZ7hdJJTENiRfBrnQzbmKmKhw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/@wekanteam/meteor-reactive-cache/-/meteor-reactive-cache-1.0.6.tgz", + "integrity": "sha512-xewS5N2ON5oN1+HWaAZhnSF7oNR/yfcXfSunVxjrCSExu3OzD1JMGK5FTxGYYzN3ShJgWGLSmjw7zL6+gvQxgg==", "requires": { - "@wekanteam/meteor-globals": "^1.1.6" + "@wekanteam/meteor-globals": "^1.1.4" } }, - "@xmldom/xmldom": { - "version": "0.9.8", - "resolved": "https://registry.npmjs.org/@xmldom/xmldom/-/xmldom-0.9.8.tgz", - "integrity": "sha512-p96FSY54r+WJ50FIOsCOjyj/wavs8921hG5+kVMmZgKcvIKxMXHTrjNJvRgWa/zuX3B6t2lijLNFaOyuxUH+2A==" - }, "abbrev": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-3.0.1.tgz", - "integrity": "sha512-AO2ac6pjRB3SJmGJo+v5/aK6Omggp6fsLrs6wN9bd35ulu4cCwaAU9+7ZhXjeqHVkaHThLuzH0nZr0YpCDhygg==" + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==" + }, + "abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "requires": { + "event-target-shim": "^5.0.0" + } }, "abstract-logging": { "version": "2.0.1", @@ -242,24 +191,27 @@ "integrity": "sha512-2BjRTZxTPvheOvGbBslFSYOUkr+SjPtOnrLP33f+VIWLzezQpZcqVg7ja3L4dBXmzzgwT+a029jRx5PCi3JuiA==" }, "acorn": { - "version": "8.16.0", - "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.16.0.tgz", - "integrity": "sha512-UVJyE9MttOsBQIDKw1skb9nAwQuR5wuGD3+82K6JgJlm/Y+KI92oNsMNGZCYdDsVtRHSak0pcV5Dno5+4jh9sw==" + "version": "8.15.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.15.0.tgz", + "integrity": "sha512-NZyJarBfL7nWwIq+FDL6Zp/yHEhePMNnnJ0y3qfieCrmNvYct8uvtiV41UvlSe6apAfk0fY1FbWx+NwfmpvtTg==" }, "agent-base": { - "version": "7.1.4", - "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-7.1.4.tgz", - "integrity": "sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==" + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz", + "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==", + "requires": { + "debug": "4" + } }, "ajv": { - "version": "8.18.0", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", - "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", "requires": { - "fast-deep-equal": "^3.1.3", - "fast-uri": "^3.0.1", - "json-schema-traverse": "^1.0.0", - "require-from-string": "^2.0.2" + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" } }, "ansi-colors": { @@ -267,6 +219,16 @@ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.3.tgz", "integrity": "sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==" }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "aproba": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz", + "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==" + }, "archiver": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/archiver/-/archiver-5.3.2.tgz", @@ -311,9 +273,31 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } } } }, + "are-we-there-yet": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz", + "integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==", + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^3.6.0" + } + }, "argparse": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", @@ -337,14 +321,6 @@ "resolved": "https://registry.npmjs.org/async/-/async-3.2.6.tgz", "integrity": "sha512-htCUDlxyyCLMgaM3xXg0C0LW2xqfuQ6p05pCEIsXuyQ+a1koYKTuBMzRNwmybfLgvJDMd0r1LTn4+E0Ti6C2AA==" }, - "available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "requires": { - "possible-typed-array-names": "^1.0.0" - } - }, "backoff": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/backoff/-/backoff-2.5.0.tgz", @@ -448,17 +424,6 @@ "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz", "integrity": "sha512-9q/rDEGSb/Qsvv2qvzIzdluL5k7AaJOTrw23z9reQthrbF7is4CtlT0DXyO1oei2DCp4uojjzQ7igaSHp1kAEQ==" }, - "call-bind": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", - "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "requires": { - "call-bind-apply-helpers": "^1.0.0", - "es-define-property": "^1.0.0", - "get-intrinsic": "^1.2.4", - "set-function-length": "^1.2.2" - } - }, "call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -475,6 +440,48 @@ "requires": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" + }, + "dependencies": { + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + } + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + } } }, "chainsaw": { @@ -486,9 +493,9 @@ } }, "chart.js": { - "version": "4.5.1", - "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.1.tgz", - "integrity": "sha512-GIjfiT9dbmHRiYi6Nl2yFCq7kkwdkp1W/lp2J99rX0yo9tgJGn3lKQATztIjb5tVtevcBtIdICNWqlq5+E8/Pw==", + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chart.js/-/chart.js-4.5.0.tgz", + "integrity": "sha512-aYeC/jDgSEx8SHWZvANYMioYMZ2KX02W6f6uVfyteuCGcadDLcYVHdfdygsTQkQ4TKn5lghoojAsPj5pu0SnvQ==", "requires": { "@kurkle/color": "^0.3.0" } @@ -520,15 +527,20 @@ } }, "chownr": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/chownr/-/chownr-3.0.0.tgz", - "integrity": "sha512-+IxzY9BZOQd/XuYPRmrvEVjF/nqj5kgT4kEq7VofrDoM1MxoRjEWkrCC3EtLi59TVawxTAn+orJwFQcrqEN1+g==" + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz", + "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==" }, "clone": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==" }, + "color-support": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz", + "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==" + }, "commander": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/commander/-/commander-6.2.1.tgz", @@ -550,10 +562,10 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" }, - "consola": { - "version": "3.4.2", - "resolved": "https://registry.npmjs.org/consola/-/consola-3.4.2.tgz", - "integrity": "sha512-5IKcdX0nnYavi6G7TtOhwkYzyjfJlatbjMjuLSfE2kYT5pMDOilZ4OvMhi637CcDICTmz3wARPoyhqyX1Y+XvA==" + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==" }, "core-util-is": { "version": "1.0.3", @@ -587,42 +599,37 @@ } }, "css-what": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.2.2.tgz", - "integrity": "sha512-u/O3vwbptzhMs3L1fQE82ZSLHQQfto5gyZzwteVIEyeaY5Fc7R4dapF/BvRoSYFeqfBk4m0V1Vafq5Pjv25wvA==" + "version": "6.1.0", + "resolved": "https://registry.npmjs.org/css-what/-/css-what-6.1.0.tgz", + "integrity": "sha512-HTUrgRJ7r4dsZKU6GjmpfRK1O76h97Z8MfS1G0FozR+oF2kG6Vfe8JE6zwrkbxigziPHinCJ+gCPjA9EaBDtRw==" }, "dayjs": { - "version": "1.11.19", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", - "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==" + "version": "1.11.18", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.18.tgz", + "integrity": "sha512-zFBQ7WFRvVRhKcWoUh+ZA1g2HVgUbsZm9sbddh8EC5iv93sui8DVVz1Npvz+r6meo9VKfa8NyLWBsQK1VvIKPA==" }, "debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", "requires": { - "ms": "^2.1.3" + "ms": "2.1.2" } }, - "define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "requires": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - } + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==" }, "detect-libc": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", - "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==" + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", + "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==" }, "diff": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", - "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/diff/-/diff-5.1.0.tgz", + "integrity": "sha512-D+mk+qE8VC/PAUrlAU34N+VfXev0ghe5ywmpqrawphmVZc1bEfn56uo9qpyGp1p4xpzOHkSW4ztBd6L7Xx4ACw==", "dev": true }, "dom-serializer": { @@ -649,9 +656,9 @@ } }, "dompurify": { - "version": "3.3.2", - "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.3.2.tgz", - "integrity": "sha512-6obghkliLdmKa56xdbLOpUZ43pAR6xFy1uOrxBaIDjT+yaRuuybLjGS9eVBoSR/UPU5fq3OXClEHLJNGvbxKpQ==", + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-3.2.7.tgz", + "integrity": "sha512-WhL/YuveyGXJaerVlMYGWhvQswa7myDG17P7Vu65EWC05o8vfeNbvNf4d/BOvH99+ZW+LlQsc1GDKMa1vNK6dw==", "requires": { "@types/trusted-types": "^2.0.7" } @@ -674,6 +681,13 @@ "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", "gopd": "^1.2.0" + }, + "dependencies": { + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + } } }, "duplexer2": { @@ -697,9 +711,27 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } } } }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==" + }, "end-of-stream": { "version": "1.4.5", "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", @@ -714,9 +746,26 @@ "integrity": "sha512-hCx1oky9PFrJ611mf0ifBLBRW8lUUVRlFolb5gWRfIELabBlbp9xZvrqZLZAs+NxFnbfQoeGd8wDkygjg7U85w==" }, "es-define-property": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", - "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.0.tgz", + "integrity": "sha512-jxayLKShrEqqzJ0eumQbVhTYQM27CfT1T35+gCgDFoL82JLsXqTJ76zv6A0YLOgEnLUMvLzsDsGIrl8NFpT2gQ==", + "requires": { + "get-intrinsic": "^1.2.4" + }, + "dependencies": { + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + } + } }, "es-errors": { "version": "1.3.0", @@ -756,10 +805,15 @@ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" }, - "eventemitter3": { - "version": "5.0.4", - "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.4.tgz", - "integrity": "sha512-mlsTRyGaPBjPedk6Bvw+aqbsXDtoAyAzm5MO7JgU+yVRyMQ5O8bD4Kcci7BS85f93veegeCPkL8R4GLClnjLFw==" + "event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==" + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, "extsprintf": { "version": "1.4.1", @@ -780,10 +834,10 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" }, - "fast-uri": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/fast-uri/-/fast-uri-3.1.0.tgz", - "integrity": "sha512-iPeeDKJSWf4IEOasVVrknXpaBV0IApz/gp7S2bb7Z4Lljbl2MGJRqInZiUrQwV16cpzw/D3S5j5Julj/gT52AA==" + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" }, "fibers": { "version": "5.0.3", @@ -801,14 +855,13 @@ } }, "file-type": { - "version": "21.3.1", - "resolved": "https://registry.npmjs.org/file-type/-/file-type-21.3.1.tgz", - "integrity": "sha512-SrzXX46I/zsRDjTb82eucsGg0ODq2NpGDp4HcsFKApPy8P8vACjpJRDoGGMfEzhFC0ry61ajd7f72J3603anBA==", + "version": "16.5.4", + "resolved": "https://registry.npmjs.org/file-type/-/file-type-16.5.4.tgz", + "integrity": "sha512-/yFHK0aGjFEgDJjEKP0pWCplsPFPhwyfwevf/pVxiN0tmE4L9LmwWxWukdJSHdoCli4VgQLehjJtwQBnqmsKcw==", "requires": { - "@tokenizer/inflate": "^0.4.1", - "strtok3": "^10.3.4", - "token-types": "^6.1.1", - "uint8array-extras": "^1.4.0" + "readable-web-to-node-stream": "^3.0.0", + "strtok3": "^6.2.4", + "token-types": "^4.1.1" } }, "filesize": { @@ -817,24 +870,34 @@ "integrity": "sha512-pjmC+bkIF8XI7fWaH8KxHcZL3DPybs1roSKP4rKDvy20tAWwIObE4+JIseG2byfGKhud5ZnM4YSGKBz7Sh0ndQ==" }, "flatted": { - "version": "3.3.4", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.4.tgz", - "integrity": "sha512-3+mMldrTAPdta5kjX2G2J7iX4zxtnwpdA8Tr2ZSjkyPSanvbZAcy6flmtnXbEybHrDcU9641lxrMfFuUxVz9vA==", + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", + "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", "dev": true }, - "for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "requires": { - "is-callable": "^1.2.7" - } - }, "fs-constants": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==" }, + "fs-minipass": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz", + "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==", + "requires": { + "minipass": "^3.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + } + } + }, "fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", @@ -849,6 +912,24 @@ "inherits": "~2.0.0", "mkdirp": ">=0.5 0", "rimraf": "2" + }, + "dependencies": { + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "requires": { + "minimist": "^1.2.6" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "requires": { + "glob": "^7.1.3" + } + } } }, "function-bind": { @@ -856,21 +937,31 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, - "get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "gauge": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz", + "integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==", + "requires": { + "aproba": "^1.0.3 || ^2.0.0", + "color-support": "^1.1.2", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.1", + "object-assign": "^4.1.1", + "signal-exit": "^3.0.0", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wide-align": "^1.1.2" + } + }, + "get-intrinsic": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.2.tgz", + "integrity": "sha512-0gSo4ml/0j98Y3lngkFEot/zhiCeWsbYIlZ+uZOVgzLyLaUw7wxUL+nCTP0XJvJg1AXulJRI3UJi8GsbDuxdGA==", "requires": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" } }, "get-proto": { @@ -896,9 +987,12 @@ } }, "gopd": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", - "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "requires": { + "get-intrinsic": "^1.1.3" + } }, "graceful-fs": { "version": "4.2.11", @@ -911,45 +1005,29 @@ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", "dev": true }, - "has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "requires": { - "es-define-property": "^1.0.0" - } + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==" }, "has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==" }, - "has-tostringtag": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", - "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "requires": { - "has-symbols": "^1.0.3" - } + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==" }, "hasown": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", - "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.0.tgz", + "integrity": "sha512-vUptKVTpIJhcczKBbgnS+RtcuYMB8+oNzPK2/Hp3hanz8JmpATdmmgLgSaadVREkDm+e2giHwY3ZRkyjSIDDFA==", "requires": { "function-bind": "^1.1.2" } }, - "hepburn": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/hepburn/-/hepburn-1.2.2.tgz", - "integrity": "sha512-DeykBc4XmfAWsnN+Y1Svi9uaQnnz21Q/ARuGWvIBxP1iUFeMIWL41DfVkgTh7tU23LFIbmIBO2Bk17BTPu0kVA==" - }, - "hotkeys-js": { - "version": "3.13.15", - "resolved": "https://registry.npmjs.org/hotkeys-js/-/hotkeys-js-3.13.15.tgz", - "integrity": "sha512-gHh8a/cPTCpanraePpjRxyIlxDFrIhYqjuh01UHWEwDpglJKCnvLW8kqSx5gQtOuSsJogNZXLhOdbSExpgUiqg==" - }, "htmlparser2": { "version": "6.1.0", "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-6.1.0.tgz", @@ -962,11 +1040,11 @@ } }, "https-proxy-agent": { - "version": "7.0.6", - "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-7.0.6.tgz", - "integrity": "sha512-vK9P5/iUfdl95AI+JVyUuIcVtd4ofvtrOr3HNtM2yxC9bnMbEdp3x01OhQNnjb8IJYi38VlTE3mBXwcfvywuSw==", + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz", + "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==", "requires": { - "agent-base": "^7.1.2", + "agent-base": "6", "debug": "4" } }, @@ -1012,6 +1090,11 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==" + }, "is-reference": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-reference/-/is-reference-1.2.1.tgz", @@ -1020,14 +1103,6 @@ "@types/estree": "*" } }, - "is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "requires": { - "which-typed-array": "^1.1.16" - } - }, "isarray": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", @@ -1039,17 +1114,17 @@ "integrity": "sha512-m4avr8yL8kmFN8psrbFFFmB/If14iN5o9nw/NgnnM+kybDJpRsAynV2BsfpTYrTRysYUdADVD7CkUUizgkpLfg==" }, "jquery-ui": { - "version": "1.14.2", - "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.14.2.tgz", - "integrity": "sha512-1gSl7PUjyipa2adSr780Ujk16faicrV7PjPPzPtvWk7tTqBnsqp67NNV9jZK2+BIxUPXWSnIUU/LBCgwgGZE+Q==", + "version": "1.13.3", + "resolved": "https://registry.npmjs.org/jquery-ui/-/jquery-ui-1.13.3.tgz", + "integrity": "sha512-D2YJfswSJRh/B8M/zCowDpNFfwsDmtfnMPwjJTyvl+CBqzpYwQ+gFYIbUUlzijy/Qvoy30H1YhoSui4MNYpRwA==", "requires": { - "jquery": ">=1.12.0 <5.0.0" + "jquery": ">=1.8.0 <4.0.0" } }, "json-schema-traverse": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", - "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==" + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" }, "jszip": { "version": "3.10.1", @@ -1075,6 +1150,19 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } } } }, @@ -1090,6 +1178,12 @@ "web-resource-inliner": "^6.0.1" } }, + "just-extend": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/just-extend/-/just-extend-4.2.1.tgz", + "integrity": "sha512-g3UB796vUFIY90VIv/WX3L2c8CS2MdWUww3CNrYmqza1Fg0DURc2K/O4YrnklBdQarSJ/y8JnJYDGc+1iumQjg==", + "dev": true + }, "lazystream": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lazystream/-/lazystream-1.0.1.tgz", @@ -1111,6 +1205,19 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } } } }, @@ -1145,17 +1252,6 @@ "immediate": "~3.0.5" } }, - "limax": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/limax/-/limax-4.1.0.tgz", - "integrity": "sha512-vciK5Mx+y+GrJjcVjbEjItzZ6Pbt+LXCb9d3qo3B+HcnTLZYRFyuszD6Hbwk0PDVEmZzS+FA0nT5aBy1HlZgGg==", - "requires": { - "hepburn": "^1.2.0", - "lodash.deburr": "^4.1.0", - "pinyin-pro": "^3.14.0", - "speakingurl": "^14.0.1" - } - }, "linkify-it": { "version": "3.0.3", "resolved": "https://registry.npmjs.org/linkify-it/-/linkify-it-3.0.3.tgz", @@ -1169,11 +1265,6 @@ "resolved": "https://registry.npmjs.org/listenercount/-/listenercount-1.0.1.tgz", "integrity": "sha512-3mk/Zag0+IJxeDrxSgaDPy4zZ3w05PRZeJNnlWhzFz5OkX49J4krc+A8X2d2M69vGMBEX0uyl8M+W+8gH+kBqQ==" }, - "lodash.deburr": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/lodash.deburr/-/lodash.deburr-4.1.0.tgz", - "integrity": "sha512-m/M1U1f3ddMCs6Hq2tAsYThTBDaAKFDX3dwDo97GEYzamXi9SqUpjWi/Rrj/gf3X2n8ktwgZrlP1z6E3v/IExQ==" - }, "lodash.defaults": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/lodash.defaults/-/lodash.defaults-4.2.0.tgz", @@ -1194,6 +1285,12 @@ "resolved": "https://registry.npmjs.org/lodash.flatten/-/lodash.flatten-4.4.0.tgz", "integrity": "sha512-C5N2Z3DgnnKr0LOpv/hKCgKdb7ZZwafIrsesve6lmzvZIRZRGaZ/l6Q8+2W7NaT+ZwO3fFlSCzCzrDCFdJfZ4g==" }, + "lodash.get": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/lodash.get/-/lodash.get-4.4.2.tgz", + "integrity": "sha512-z+Uw/vLuy6gQe8cfaFWD7p0wVv8fJl3mbzXh33RS+0oW2wvUqiRXiQ69gLWSLpgB5/6sU+r6BlQR0MBILadqTQ==", + "dev": true + }, "lodash.groupby": { "version": "4.6.0", "resolved": "https://registry.npmjs.org/lodash.groupby/-/lodash.groupby-4.6.0.tgz", @@ -1239,6 +1336,14 @@ "resolved": "https://registry.npmjs.org/lodash.uniq/-/lodash.uniq-4.5.0.tgz", "integrity": "sha512-xfBaXQd9ryd9dlSDvnvI0lvxfLJlYAZzXomUYzLKtUeOQvOP5piqAWuGtrhWeqaXK9hhoM/iyJc5AV+XfsX3HQ==" }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "requires": { + "yallist": "^4.0.0" + } + }, "magic-string": { "version": "0.25.9", "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.25.9.tgz", @@ -1247,6 +1352,21 @@ "sourcemap-codec": "^1.4.8" } }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "requires": { + "semver": "^6.0.0" + }, + "dependencies": { + "semver": { + "version": "6.3.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz", + "integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==" + } + } + }, "markdown-it": { "version": "12.3.2", "resolved": "https://registry.npmjs.org/markdown-it/-/markdown-it-12.3.2.tgz", @@ -1305,9 +1425,9 @@ "integrity": "sha512-SBbbYWvFYvsxHVL+q6ZB8lT3rp2LSvfALD2V52H+MGH2IgJsevy0VtXRkRG0EsUewwOaDTIKBn9DlD8HQ3GSwg==" }, "meteor-node-stubs": { - "version": "1.2.27", - "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-1.2.27.tgz", - "integrity": "sha512-FiT2i2DBiArWAL+BiVze9vUdKrpI3IuPH76hLm8d/KMTCQyqXYOFDS8VWFUxFvMsrpXkFloOiNaHkhiTjzI5mw==", + "version": "1.2.24", + "resolved": "https://registry.npmjs.org/meteor-node-stubs/-/meteor-node-stubs-1.2.24.tgz", + "integrity": "sha512-tw9QzDFVOI5A5CcEw4tTD6CjF+Lk14uzhy2gWH5ImoH4mx4pyPVcha9MmyVur+rEVgpzk+aMG6rs3RxAF9SwiA==", "requires": { "@meteorjs/crypto-browserify": "^3.12.1", "assert": "^2.1.0", @@ -1337,7 +1457,8 @@ "dependencies": { "@meteorjs/browserify-sign": { "version": "4.2.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/@meteorjs/browserify-sign/-/browserify-sign-4.2.6.tgz", + "integrity": "sha512-xnQRbIrjHxaVbOEbzbcdav4QDRTnfRAVHi21SPosnGNiIHTdTeGQGmTF/f7GwntxqynabSifdBHeGA7W8lIKSQ==", "requires": { "bn.js": "^5.2.1", "brorand": "^1.1.0", @@ -1357,11 +1478,13 @@ "dependencies": { "isarray": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==" }, "readable-stream": { "version": "2.3.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "requires": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -1374,20 +1497,23 @@ "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } }, "string_decoder": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "requires": { "safe-buffer": "~5.1.0" }, "dependencies": { "safe-buffer": { "version": "5.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" } } } @@ -1395,7 +1521,8 @@ }, "@meteorjs/create-ecdh": { "version": "4.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/@meteorjs/create-ecdh/-/create-ecdh-4.0.5.tgz", + "integrity": "sha512-dhn3AICsDlIZ5qY/Qu+QOL+ZGKaHcGss4PQ3CfmAF3f+o5fPJ2aDJcxd5f2au2k6sxyNqvCsLAFYFHXxHoH9yQ==", "requires": { "bn.js": "^4.11.9", "brorand": "^1.1.0", @@ -1407,14 +1534,16 @@ }, "dependencies": { "bn.js": { - "version": "4.12.3", - "bundled": true + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==" } } }, "@meteorjs/crypto-browserify": { "version": "3.12.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/@meteorjs/crypto-browserify/-/crypto-browserify-3.12.4.tgz", + "integrity": "sha512-K5Sgvxef93Zrw5T9cJxKuNVgpl1C2W8cfcicN6HKy98G6RoIrx6hikwWnq8FlagvOzdIQEC2s+SMn7UFNSK0eA==", "requires": { "@meteorjs/browserify-sign": "^4.2.3", "@meteorjs/create-ecdh": "^4.0.4", @@ -1432,7 +1561,8 @@ }, "asn1.js": { "version": "4.10.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", "requires": { "bn.js": "^4.0.0", "inherits": "^2.0.1", @@ -1440,14 +1570,16 @@ }, "dependencies": { "bn.js": { - "version": "4.12.3", - "bundled": true + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==" } } }, "assert": { "version": "2.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", + "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", "requires": { "call-bind": "^1.0.2", "is-nan": "^1.3.2", @@ -1458,26 +1590,31 @@ }, "available-typed-arrays": { "version": "1.0.7", - "bundled": true, + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "requires": { "possible-typed-array-names": "^1.0.0" } }, "base64-js": { "version": "1.5.1", - "bundled": true + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==" }, "bn.js": { - "version": "5.2.3", - "bundled": true + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.2.tgz", + "integrity": "sha512-v2YAxEmKaBLahNwE1mjp4WON6huMNeuDvagFZW+ASCuA/ku0bXR9hSMw0XpiqMoA3+rmnyck/tPRSFQkoC9Cuw==" }, "brorand": { "version": "1.1.0", - "bundled": true + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==" }, "browserify-aes": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "requires": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -1489,7 +1626,8 @@ }, "browserify-cipher": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", "requires": { "browserify-aes": "^1.0.4", "browserify-des": "^1.0.0", @@ -1498,7 +1636,8 @@ }, "browserify-des": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", "requires": { "cipher-base": "^1.0.1", "des.js": "^1.0.0", @@ -1508,7 +1647,8 @@ }, "browserify-rsa": { "version": "4.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", "requires": { "bn.js": "^5.2.1", "randombytes": "^2.1.0", @@ -1517,14 +1657,16 @@ }, "browserify-zlib": { "version": "0.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/browserify-zlib/-/browserify-zlib-0.2.0.tgz", + "integrity": "sha512-Z942RysHXmJrhqk88FmKBVq/v5tqmSkDz7p54G/MGyjMnCFFnC79XWNbg+Vta8W6Wb2qtSZTSxIGkJrRpCFEiA==", "requires": { "pako": "~1.0.5" } }, "buffer": { "version": "5.7.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", "requires": { "base64-js": "^1.3.1", "ieee754": "^1.1.13" @@ -1532,15 +1674,18 @@ }, "buffer-xor": { "version": "1.0.3", - "bundled": true + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==" }, "builtin-status-codes": { "version": "3.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/builtin-status-codes/-/builtin-status-codes-3.0.0.tgz", + "integrity": "sha512-HpGFw18DgFWlncDfjTa2rcQ4W88O1mC8e8yZ2AvQY5KDaktSTwo+KRf6nHK6FRI5FyRyb/5T6+TSxfP7QyGsmQ==" }, "call-bind": { "version": "1.0.8", - "bundled": true, + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "requires": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", @@ -1550,7 +1695,8 @@ }, "call-bind-apply-helpers": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "requires": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -1558,7 +1704,8 @@ }, "call-bound": { "version": "1.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "requires": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -1566,7 +1713,8 @@ }, "cipher-base": { "version": "1.0.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.6.tgz", + "integrity": "sha512-3Ek9H3X6pj5TgenXYtNWdaBon1tgYCaebd+XPg0keyjEbEfkD4KkmAxkQ/i1vYvxdcT5nscLBfq9VJRmCBcFSw==", "requires": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1" @@ -1574,19 +1722,23 @@ }, "console-browserify": { "version": "1.2.0", - "bundled": true + "resolved": "https://registry.npmjs.org/console-browserify/-/console-browserify-1.2.0.tgz", + "integrity": "sha512-ZMkYO/LkF17QvCPqM0gxw8yUzigAOZOSWSHg91FH6orS7vcEj5dVZTidN2fQ14yBSdg97RqhSNwLUXInd52OTA==" }, "constants-browserify": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/constants-browserify/-/constants-browserify-1.0.0.tgz", + "integrity": "sha512-xFxOwqIzR/e1k1gLiWEophSCMqXcwVHIH7akf7b/vxcUeGunlj3hvZaaqxwHsTgn+IndtkQJgSztIDWeumWJDQ==" }, "core-util-is": { "version": "1.0.3", - "bundled": true + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==" }, "create-hash": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", + "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "requires": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -1597,7 +1749,8 @@ }, "create-hmac": { "version": "1.1.7", - "bundled": true, + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "requires": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -1609,7 +1762,8 @@ }, "define-data-property": { "version": "1.1.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "requires": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -1618,7 +1772,8 @@ }, "define-properties": { "version": "1.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", "requires": { "define-data-property": "^1.0.1", "has-property-descriptors": "^1.0.0", @@ -1627,7 +1782,8 @@ }, "des.js": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", "requires": { "inherits": "^2.0.1", "minimalistic-assert": "^1.0.0" @@ -1635,7 +1791,8 @@ }, "diffie-hellman": { "version": "5.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", "requires": { "bn.js": "^4.1.0", "miller-rabin": "^4.0.0", @@ -1643,18 +1800,21 @@ }, "dependencies": { "bn.js": { - "version": "4.12.3", - "bundled": true + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==" } } }, "domain-browser": { "version": "4.23.0", - "bundled": true + "resolved": "https://registry.npmjs.org/domain-browser/-/domain-browser-4.23.0.tgz", + "integrity": "sha512-ArzcM/II1wCCujdCNyQjXrAFwS4mrLh4C7DZWlaI8mdh7h3BfKdNd3bKXITfl2PT9FtfQqaGvhi1vPRQPimjGA==" }, "dunder-proto": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", "requires": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -1663,26 +1823,31 @@ }, "es-define-property": { "version": "1.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" }, "es-errors": { "version": "1.3.0", - "bundled": true + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==" }, "es-object-atoms": { "version": "1.1.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "requires": { "es-errors": "^1.3.0" } }, "events": { "version": "3.3.0", - "bundled": true + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==" }, "evp_bytestokey": { "version": "1.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", + "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "requires": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -1690,18 +1855,21 @@ }, "for-each": { "version": "0.3.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "requires": { "is-callable": "^1.2.7" } }, "function-bind": { "version": "1.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==" }, "get-intrinsic": { "version": "1.3.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "requires": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -1717,7 +1885,8 @@ }, "get-proto": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "requires": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -1725,29 +1894,34 @@ }, "gopd": { "version": "1.2.0", - "bundled": true + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" }, "has-property-descriptors": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "requires": { "es-define-property": "^1.0.0" } }, "has-symbols": { "version": "1.1.0", - "bundled": true + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" }, "has-tostringtag": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "requires": { "has-symbols": "^1.0.3" } }, "hash-base": { "version": "3.0.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.0.5.tgz", + "integrity": "sha512-vXm0l45VbcHEVlTCzs8M+s0VeYsB2lnlAaThoLKGXr3bE/VWDOelNUnycUPEhKEaXARL2TEFjBOyUiM6+55KBg==", "requires": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1" @@ -1755,7 +1929,8 @@ }, "hash.js": { "version": "1.1.7", - "bundled": true, + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", "requires": { "inherits": "^2.0.3", "minimalistic-assert": "^1.0.1" @@ -1763,14 +1938,16 @@ }, "hasown": { "version": "2.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "requires": { "function-bind": "^1.1.2" } }, "hmac-drbg": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", "requires": { "hash.js": "^1.0.3", "minimalistic-assert": "^1.0.0", @@ -1779,19 +1956,23 @@ }, "https-browserify": { "version": "1.0.0", - "bundled": true + "resolved": "https://registry.npmjs.org/https-browserify/-/https-browserify-1.0.0.tgz", + "integrity": "sha512-J+FkSdyD+0mA0N+81tMotaRMfSL9SGi+xpD3T6YApKsc3bGSXJlfXri3VyFOeYkfLRQisDk1W+jIFFKBeUBbBg==" }, "ieee754": { "version": "1.2.1", - "bundled": true + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==" }, "inherits": { "version": "2.0.4", - "bundled": true + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "is-arguments": { "version": "1.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", "requires": { "call-bound": "^1.0.2", "has-tostringtag": "^1.0.2" @@ -1799,11 +1980,13 @@ }, "is-callable": { "version": "1.2.7", - "bundled": true + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==" }, "is-generator-function": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.0.tgz", + "integrity": "sha512-nPUB5km40q9e8UfN/Zc24eLlzdSf9OfKByBw9CIdw4H1giPMeA0OIJvbchsCu4npfI2QcMVBsGEBHKZ7wLTWmQ==", "requires": { "call-bound": "^1.0.3", "get-proto": "^1.0.0", @@ -1813,7 +1996,8 @@ }, "is-nan": { "version": "1.3.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", + "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", "requires": { "call-bind": "^1.0.0", "define-properties": "^1.1.3" @@ -1821,7 +2005,8 @@ }, "is-regex": { "version": "1.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", "requires": { "call-bound": "^1.0.2", "gopd": "^1.2.0", @@ -1831,22 +2016,26 @@ }, "is-typed-array": { "version": "1.1.15", - "bundled": true, + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "requires": { "which-typed-array": "^1.1.16" } }, "isarray": { "version": "2.0.5", - "bundled": true + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" }, "math-intrinsics": { "version": "1.1.0", - "bundled": true + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==" }, "md5.js": { "version": "1.3.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -1855,33 +2044,39 @@ }, "miller-rabin": { "version": "4.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", "requires": { "bn.js": "^4.0.0", "brorand": "^1.0.1" }, "dependencies": { "bn.js": { - "version": "4.12.3", - "bundled": true + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==" } } }, "minimalistic-assert": { "version": "1.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==" }, "minimalistic-crypto-utils": { "version": "1.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==" }, "object-inspect": { "version": "1.13.4", - "bundled": true + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" }, "object-is": { "version": "1.1.6", - "bundled": true, + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", "requires": { "call-bind": "^1.0.7", "define-properties": "^1.2.1" @@ -1889,11 +2084,13 @@ }, "object-keys": { "version": "1.1.1", - "bundled": true + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==" }, "object.assign": { "version": "4.1.7", - "bundled": true, + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", "requires": { "call-bind": "^1.0.8", "call-bound": "^1.0.3", @@ -1905,15 +2102,18 @@ }, "os-browserify": { "version": "0.3.0", - "bundled": true + "resolved": "https://registry.npmjs.org/os-browserify/-/os-browserify-0.3.0.tgz", + "integrity": "sha512-gjcpUc3clBf9+210TRaDWbf+rZZZEshZ+DlXMRCeAjp0xhTrnQsKHypIy1J3d5hKdUzj69t708EHtU8P6bUn0A==" }, "pako": { "version": "1.0.11", - "bundled": true + "resolved": "https://registry.npmjs.org/pako/-/pako-1.0.11.tgz", + "integrity": "sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==" }, "parse-asn1": { "version": "5.1.7", - "bundled": true, + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.7.tgz", + "integrity": "sha512-CTM5kuWR3sx9IFamcl5ErfPl6ea/N8IYwiJ+vpeB2g+1iknv7zBl5uPwbMbRVznRVbrNY6lGuDoE5b30grmbqg==", "requires": { "asn1.js": "^4.10.1", "browserify-aes": "^1.2.0", @@ -1925,11 +2125,13 @@ }, "path-browserify": { "version": "1.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/path-browserify/-/path-browserify-1.0.1.tgz", + "integrity": "sha512-b7uo2UCUOYZcnF/3ID0lulOJi/bafxa1xPe7ZPsammBSpjSWQkjNxlt635YGS2MiR9GjvuXCtz2emr3jbsz98g==" }, "pbkdf2": { "version": "3.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.3.tgz", + "integrity": "sha512-wfRLBZ0feWRhCIkoMB6ete7czJcnNnqRpcoWQBLqatqXXmelSRqfdDK4F3u9T2s2cXas/hQJcryI/4lAL+XTlA==", "requires": { "create-hash": "~1.1.3", "create-hmac": "^1.1.7", @@ -1941,7 +2143,8 @@ "dependencies": { "create-hash": { "version": "1.1.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.1.3.tgz", + "integrity": "sha512-snRpch/kwQhcdlnZKYanNF1m0RDlrCdSKQaH87w1FCFPVPNCQ/Il9QJKAX2jVBZddRdaHBMC+zXa9Gw9tmkNUA==", "requires": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -1951,14 +2154,16 @@ }, "hash-base": { "version": "2.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-2.0.2.tgz", + "integrity": "sha512-0TROgQ1/SxE6KmxWSvXHvRj90/Xo1JvZShofnYF+f6ZsGtR4eES7WfrQzPalmyagfKZCXpVnitiRebZulWsbiw==", "requires": { "inherits": "^2.0.1" } }, "ripemd160": { "version": "2.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.1.tgz", + "integrity": "sha512-J7f4wutN8mdbV08MJnXibYpCOPHR+yzy+iQ/AsjMv2j8cLavQ8VGagDFUwwTAdF8FmRKVeNpbTTEwNHCW1g94w==", "requires": { "hash-base": "^2.0.0", "inherits": "^2.0.1" @@ -1968,19 +2173,23 @@ }, "possible-typed-array-names": { "version": "1.1.0", - "bundled": true + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==" }, "process": { "version": "0.11.10", - "bundled": true + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" }, "process-nextick-args": { "version": "2.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, "public-encrypt": { "version": "4.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", "requires": { "bn.js": "^4.1.0", "browserify-rsa": "^4.0.0", @@ -1991,36 +2200,42 @@ }, "dependencies": { "bn.js": { - "version": "4.12.3", - "bundled": true + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==" } } }, "punycode": { "version": "1.4.1", - "bundled": true + "resolved": "https://registry.npmjs.org/punycode/-/punycode-1.4.1.tgz", + "integrity": "sha512-jmYNElW7yvO7TV33CjSmvSiE2yco3bV2czu/OzDKdMNVZQWfxCblURLhf+47syQRBntjfLdd/H0egrzIG+oaFQ==" }, "qs": { - "version": "6.14.2", - "bundled": true, + "version": "6.14.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.0.tgz", + "integrity": "sha512-YWWTjgABSKcvs/nWBi9PycY/JiPJqOD4JA6o9Sej2AtvSGarXxKC3OQSk4pAarbdQlKAh5D4FCQkJNkW+GAn3w==", "requires": { "side-channel": "^1.1.0" } }, "querystring-es3": { "version": "0.2.1", - "bundled": true + "resolved": "https://registry.npmjs.org/querystring-es3/-/querystring-es3-0.2.1.tgz", + "integrity": "sha512-773xhDQnZBMFobEiztv8LIl70ch5MSF/jUQVlhwFyBILqq96anmoctVIYz+ZRp0qbCKATTn6ev02M3r7Ga5vqA==" }, "randombytes": { "version": "2.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", "requires": { "safe-buffer": "^5.1.0" } }, "randomfill": { "version": "1.0.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", "requires": { "randombytes": "^2.0.5", "safe-buffer": "^5.1.0" @@ -2028,7 +2243,8 @@ }, "readable-stream": { "version": "3.6.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "requires": { "inherits": "^2.0.3", "string_decoder": "^1.1.1", @@ -2037,7 +2253,8 @@ }, "ripemd160": { "version": "2.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz", + "integrity": "sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA==", "requires": { "hash-base": "^3.0.0", "inherits": "^2.0.1" @@ -2045,11 +2262,13 @@ }, "safe-buffer": { "version": "5.2.1", - "bundled": true + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safe-regex-test": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", "requires": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -2058,7 +2277,8 @@ }, "set-function-length": { "version": "1.2.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "requires": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -2070,11 +2290,13 @@ }, "setimmediate": { "version": "1.0.5", - "bundled": true + "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", + "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, "sha.js": { "version": "2.4.12", - "bundled": true, + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", "requires": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1", @@ -2083,7 +2305,8 @@ }, "side-channel": { "version": "1.1.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "requires": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -2094,7 +2317,8 @@ }, "side-channel-list": { "version": "1.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "requires": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -2102,7 +2326,8 @@ }, "side-channel-map": { "version": "1.0.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "requires": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -2112,7 +2337,8 @@ }, "side-channel-weakmap": { "version": "1.0.2", - "bundled": true, + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "requires": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -2123,7 +2349,8 @@ }, "stream-browserify": { "version": "3.0.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", "requires": { "inherits": "~2.0.4", "readable-stream": "^3.5.0" @@ -2131,7 +2358,8 @@ }, "stream-http": { "version": "3.2.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/stream-http/-/stream-http-3.2.0.tgz", + "integrity": "sha512-Oq1bLqisTyK3TSCXpPbT4sdeYNdmyZJv1LxpEm2vu1ZhK89kSE5YXwZc3cWk0MagGaKriBh9mCFbVGtO+vY29A==", "requires": { "builtin-status-codes": "^3.0.0", "inherits": "^2.0.4", @@ -2141,21 +2369,24 @@ }, "string_decoder": { "version": "1.3.0", - "bundled": true, + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "requires": { "safe-buffer": "~5.2.0" } }, "timers-browserify": { "version": "2.0.12", - "bundled": true, + "resolved": "https://registry.npmjs.org/timers-browserify/-/timers-browserify-2.0.12.tgz", + "integrity": "sha512-9phl76Cqm6FhSX9Xe1ZUAMLtm1BLkKj2Qd5ApyWkXzsMRaA7dgr81kf4wJmQf/hAvg8EEyJxDo3du/0KlhPiKQ==", "requires": { "setimmediate": "^1.0.4" } }, "to-buffer": { "version": "1.2.1", - "bundled": true, + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", "requires": { "isarray": "^2.0.5", "safe-buffer": "^5.2.1", @@ -2164,11 +2395,13 @@ }, "tty-browserify": { "version": "0.0.1", - "bundled": true + "resolved": "https://registry.npmjs.org/tty-browserify/-/tty-browserify-0.0.1.tgz", + "integrity": "sha512-C3TaO7K81YvjCgQH9Q1S3R3P3BtN3RIM8n+OvX4il1K1zgE8ZhI0op7kClgkxtutIE8hQrcrHBXvIheqKUUCxw==" }, "typed-array-buffer": { "version": "1.0.3", - "bundled": true, + "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", + "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "requires": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -2177,7 +2410,8 @@ }, "url": { "version": "0.11.4", - "bundled": true, + "resolved": "https://registry.npmjs.org/url/-/url-0.11.4.tgz", + "integrity": "sha512-oCwdVC7mTuWiPyjLUz/COz5TLk6wgp0RCsN+wHZ2Ekneac9w8uuV0njcbbie2ME+Vs+d6duwmYuR3HgQXs1fOg==", "requires": { "punycode": "^1.4.1", "qs": "^6.12.3" @@ -2185,7 +2419,8 @@ }, "util": { "version": "0.12.5", - "bundled": true, + "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", + "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", "requires": { "inherits": "^2.0.3", "is-arguments": "^1.0.4", @@ -2196,15 +2431,18 @@ }, "util-deprecate": { "version": "1.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" }, "vm-browserify": { "version": "1.1.2", - "bundled": true + "resolved": "https://registry.npmjs.org/vm-browserify/-/vm-browserify-1.1.2.tgz", + "integrity": "sha512-2ham8XPWTONajOR0ohOKOHXkm3+gaBmGut3SRuu75xLd/RRaY6vqgh8NBYYk7+RW3u5AtzPQZG8F10LHkl0lAQ==" }, "which-typed-array": { "version": "1.1.19", - "bundled": true, + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", "requires": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", @@ -2217,7 +2455,8 @@ }, "xtend": { "version": "4.0.2", - "bundled": true + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==" } } }, @@ -2232,9 +2471,9 @@ "integrity": "sha512-USPkMeET31rOMiarsBNIHZKLGgvKc/LrjofAnBlOttf5ajRvqiRA8QsenbcooctK6d6Ts6aqZXBA+XbkKthiQg==" }, "minimatch": { - "version": "3.1.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.5.tgz", - "integrity": "sha512-VgjWUsnnT6n+NUk6eZq77zeFdpW2LWDzP6zFGrCbHXiYNul5Dzqk2HHQ5uFH2DNW5Xbp8+jVzaeNt94ssEEl4w==", + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "requires": { "brace-expansion": "^1.1.7" } @@ -2245,16 +2484,27 @@ "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==" }, "minipass": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/minipass/-/minipass-7.1.3.tgz", - "integrity": "sha512-tEBHqDnIoM/1rXME1zgka9g6Q2lcoCkxHLuc7ODJ5BxbP5d4c2Z5cGgtXAku59200Cx7diuHTOYfSBD8n6mm8A==" + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz", + "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==" }, "minizlib": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-3.1.0.tgz", - "integrity": "sha512-KZxYo1BUkWD2TVFLr0MQoM8vUUigWD3LlD83a/75BqC+4qE0Hb1Vo5v1FgcfaNXvfXzr+5EhQ6ing/CaBijTlw==", + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz", + "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==", "requires": { - "minipass": "^7.1.2" + "minipass": "^3.0.0", + "yallist": "^4.0.0" + }, + "dependencies": { + "minipass": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz", + "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==", + "requires": { + "yallist": "^4.0.0" + } + } } }, "mj-context-menu": { @@ -2263,22 +2513,78 @@ "integrity": "sha512-7NO5s6n10TIV96d4g2uDpG7ZDpIhMh0QNfGdJw/W47JswFcosz457wqz/b5sAKvl12sxINGFCn80NZHKwxQEXA==" }, "mkdirp": { - "version": "0.5.6", - "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", - "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", - "requires": { - "minimist": "^1.2.6" - } + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", + "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==" }, "mongo-object": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/mongo-object/-/mongo-object-3.0.3.tgz", - "integrity": "sha512-GeEUfvaJROzapKIC41jk1FjFfIAsrq9+spioJP/Yvxe753SiFf3Mcop0PAmg8RQusL6+wCttNMee9cWzqbt/Sw==" + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/mongo-object/-/mongo-object-3.0.1.tgz", + "integrity": "sha512-EbiwWHvKOF9xhIzuwaqknwPISdkHMipjMs6DiJFicupgBBLEhUs0OOro9MuPkFogB17DZlsV4KJhhxfqZ7ZRMQ==" }, "ms": { - "version": "2.1.3", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", - "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==" + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "nise": { + "version": "5.1.5", + "resolved": "https://registry.npmjs.org/nise/-/nise-5.1.5.tgz", + "integrity": "sha512-VJuPIfUFaXNRzETTQEEItTOP8Y171ijr+JLq42wHes3DiryR8vT+1TXQW/Rx8JNUhyYYWyIvjXTU6dOhJcs9Nw==", + "dev": true, + "requires": { + "@sinonjs/commons": "^2.0.0", + "@sinonjs/fake-timers": "^10.0.2", + "@sinonjs/text-encoding": "^0.7.1", + "just-extend": "^4.0.2", + "path-to-regexp": "^1.7.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-2.0.0.tgz", + "integrity": "sha512-uLa0j859mMrg2slwQYdO/AkrOfmH+X6LTVmNTS9CqexuE2IvVORIkSpJLqePAbEnKJ77aMmCwr1NUZ57120Xcg==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + }, + "@sinonjs/fake-timers": { + "version": "10.3.0", + "resolved": "https://registry.npmjs.org/@sinonjs/fake-timers/-/fake-timers-10.3.0.tgz", + "integrity": "sha512-V4BG07kuYSUkTCSBHG8G8TNhM+F19jXFWnQtzj+we8DrkpSBCee9Z3Ms8yiGer/dlmhe35/Xdgyo3/0rQKg7YA==", + "dev": true, + "requires": { + "@sinonjs/commons": "^3.0.0" + }, + "dependencies": { + "@sinonjs/commons": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@sinonjs/commons/-/commons-3.0.0.tgz", + "integrity": "sha512-jXBtWAF4vmdNmZgD5FoKsVLv3rPgDnLgPbU84LIJ3otV44vJlDRokVng5v8NFJdCf/da9legHcKaRuZs4L7faA==", + "dev": true, + "requires": { + "type-detect": "4.0.8" + } + } + } + }, + "isarray": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz", + "integrity": "sha512-D2S+3GLxWH+uhrNEcoh/fnmYeP8E8/zHl644d/jdA0g2uyXvy3sb0qxotE+ne0LtccHknQzWwZEzhak7oJ0COQ==", + "dev": true + }, + "path-to-regexp": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-1.9.0.tgz", + "integrity": "sha512-xIp7/apCFJuUHdDLWe8O1HIkb0kQrOMb/0u6FXQjemHn/ii5LrIzU6bdECnsiTF/GjZkMEKg1xdiZwNqDYlZ6g==", + "dev": true, + "requires": { + "isarray": "0.0.1" + } + } + } }, "node-fetch": { "version": "2.7.0", @@ -2289,11 +2595,11 @@ } }, "nopt": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/nopt/-/nopt-8.1.0.tgz", - "integrity": "sha512-ieGu42u/Qsa4TFktmaKEwM6MQH0pOWnaB3htzh0JRtx84+Mebc0cbZYN5bC+6WTZ4+77xrL9Pn5m7CV6VIkV7A==", + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz", + "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==", "requires": { - "abbrev": "^3.0.0" + "abbrev": "1" } }, "normalize-path": { @@ -2301,6 +2607,17 @@ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==" }, + "npmlog": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz", + "integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==", + "requires": { + "are-we-there-yet": "^2.0.0", + "console-control-strings": "^1.1.0", + "gauge": "^3.0.0", + "set-blocking": "^2.0.0" + } + }, "nth-check": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", @@ -2309,10 +2626,15 @@ "boolbase": "^1.0.0" } }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==" + }, "object-inspect": { - "version": "1.13.4", - "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", - "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==" + "version": "1.13.2", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.2.tgz", + "integrity": "sha512-IRZSRuzJiynemAXPYtPe5BoI/RESNYR7TYm50MC5Mqbd3Jmw5y790sErYw3V6SryFJD64b74qQQs9wn5Bg/k3g==" }, "once": { "version": "1.4.0", @@ -2360,6 +2682,11 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" }, + "peek-readable": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/peek-readable/-/peek-readable-4.1.0.tgz", + "integrity": "sha512-ZI3LnwUv5nOGbQzD9c2iDG6toheuXSZP5esSHBjopsXH4dg19soufvpUGA3uohi5anFtGb2lhAVdHzH6R/Evvg==" + }, "periscopic": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/periscopic/-/periscopic-2.0.3.tgz", @@ -2369,11 +2696,6 @@ "is-reference": "^1.1.4" } }, - "pinyin-pro": { - "version": "3.28.0", - "resolved": "https://registry.npmjs.org/pinyin-pro/-/pinyin-pro-3.28.0.tgz", - "integrity": "sha512-mMRty6RisoyYNphJrTo3pnvp3w8OMZBrXm9YSWkxhAfxKj1KZk2y8T2PDIZlDDRsvZ0No+Hz6FI4sZpA6Ey25g==" - }, "possible-typed-array-names": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", @@ -2392,17 +2714,27 @@ "parse-ms": "^2.1.0" } }, + "process": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/process/-/process-0.11.10.tgz", + "integrity": "sha512-cdGef/drWFoydD1JsMzuFf8100nZl+GT+yacc2bEced5f9Rjk4z+WtFUTBu9PhOi9j/jfmBPu0mMEY4wIdAF8A==" + }, "process-nextick-args": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==" }, + "punycode": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", + "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==" + }, "qs": { - "version": "6.15.0", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.0.tgz", - "integrity": "sha512-mAZTtNCeetKMH+pSjrb76NAM8V9a05I9aBZOHztWy/UqcJdQYNsf59vrRKWnojAT9Y+GbIvoTBC++CPHqpDBhQ==", + "version": "6.13.0", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.13.0.tgz", + "integrity": "sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==", "requires": { - "side-channel": "^1.1.0" + "side-channel": "^1.0.6" } }, "readable-stream": { @@ -2415,6 +2747,37 @@ "util-deprecate": "^1.0.1" } }, + "readable-web-to-node-stream": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/readable-web-to-node-stream/-/readable-web-to-node-stream-3.0.4.tgz", + "integrity": "sha512-9nX56alTf5bwXQ3ZDipHJhusu9NTQJ/CVPtb/XHAJCXihZeitfJvIRS4GqQ/mfIoOE3IelHMrpayVrosdHBuLw==", + "requires": { + "readable-stream": "^4.7.0" + }, + "dependencies": { + "buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "requires": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "readable-stream": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-4.7.0.tgz", + "integrity": "sha512-oIGGmcpTLwPga8Bn6/Z75SVaH1z5dUut2ibSyAMVhmUggWpmDn2dapB0n7f8nwaSiRtepAsfJyfXIO5DCVAODg==", + "requires": { + "abort-controller": "^3.0.0", + "buffer": "^6.0.3", + "events": "^3.3.0", + "process": "^0.11.10", + "string_decoder": "^1.3.0" + } + } + } + }, "readdir-glob": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/readdir-glob/-/readdir-glob-1.1.3.tgz", @@ -2432,32 +2795,27 @@ } }, "minimatch": { - "version": "5.1.9", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.9.tgz", - "integrity": "sha512-7o1wEA2RyMP7Iu7GNba9vc0RWWGACJOCZBJX2GJWip0ikV+wcOsgVuY9uE8CPiyQhkGFSlhuSkZPavN7u1c2Fw==", + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz", + "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==", "requires": { "brace-expansion": "^2.0.1" } } } }, - "require-from-string": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", - "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==" - }, "rimraf": { - "version": "2.7.1", - "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", - "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", "requires": { "glob": "^7.1.3" } }, "safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" }, "safer-buffer": { "version": "2.1.2", @@ -2473,71 +2831,95 @@ } }, "semver": { - "version": "7.7.4", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", - "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==" - }, - "set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", "requires": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" + "lru-cache": "^6.0.0" } }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==" + }, "setimmediate": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==" }, "side-channel": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", - "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.6.tgz", + "integrity": "sha512-fDW/EZ6Q9RiO8eFG8Hj+7u/oW+XrPTIChwCOM2+th2A6OblDtYYIpve9m+KvI9Z4C9qSEXlaGR6bTEYHReuglA==", "requires": { + "call-bind": "^1.0.7", "es-errors": "^1.3.0", - "object-inspect": "^1.13.3", - "side-channel-list": "^1.0.0", - "side-channel-map": "^1.0.1", - "side-channel-weakmap": "^1.0.2" + "get-intrinsic": "^1.2.4", + "object-inspect": "^1.13.1" + }, + "dependencies": { + "call-bind": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.7.tgz", + "integrity": "sha512-GHTSNSYICQ7scH7sZ+M2rFopRoLh8t2bLSW6BbgrtLsahOIB5iyAVJf9GjWK3cYTDaMj4XdBpM1cA6pIS0Kv2w==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.1" + } + }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "get-intrinsic": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.4.tgz", + "integrity": "sha512-5uYhsJH8VJBTv7oslg4BznJYhDoRI6waYCxMmCdnTrcCrHA/fCFKoTFz2JKKE0HdDFUF7/oQuhzumXJK7paBRQ==", + "requires": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3", + "hasown": "^2.0.0" + } + }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + } } }, - "side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", - "requires": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" - } - }, - "side-channel-map": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", - "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", - "requires": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3" - } - }, - "side-channel-weakmap": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", - "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", - "requires": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.5", - "object-inspect": "^1.13.3", - "side-channel-map": "^1.0.1" - } + "signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, "simpl-schema": { "version": "3.4.6", @@ -2549,15 +2931,16 @@ } }, "sinon": { - "version": "21.0.1", - "resolved": "https://registry.npmjs.org/sinon/-/sinon-21.0.1.tgz", - "integrity": "sha512-Z0NVCW45W8Mg5oC/27/+fCqIHFnW8kpkFOq0j9XJIev4Ld0mKmERaZv5DMLAb9fGCevjKwaEeIQz5+MBXfZcDw==", + "version": "13.0.2", + "resolved": "https://registry.npmjs.org/sinon/-/sinon-13.0.2.tgz", + "integrity": "sha512-KvOrztAVqzSJWMDoxM4vM+GPys1df2VBoXm+YciyB/OLMamfS3VXh3oGh5WtrAGSzrgczNWFFY22oKb7Fi5eeA==", "dev": true, "requires": { - "@sinonjs/commons": "^3.0.1", - "@sinonjs/fake-timers": "^15.1.0", - "@sinonjs/samsam": "^8.0.3", - "diff": "^8.0.2", + "@sinonjs/commons": "^1.8.3", + "@sinonjs/fake-timers": "^9.1.2", + "@sinonjs/samsam": "^6.1.1", + "diff": "^5.0.0", + "nise": "^5.1.1", "supports-color": "^7.2.0" } }, @@ -2585,42 +2968,56 @@ "resolved": "https://registry.npmjs.org/sourcemap-codec/-/sourcemap-codec-1.4.8.tgz", "integrity": "sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==" }, - "speakingurl": { - "version": "14.0.1", - "resolved": "https://registry.npmjs.org/speakingurl/-/speakingurl-14.0.1.tgz", - "integrity": "sha512-1POYv7uv2gXoyGFpBCmpDVSNV74IfsWlDW216UPjbWufNf+bSU6GdbDsxdcxtfwb4xlI3yxzOTKClUosxARYrQ==" - }, "speech-rule-engine": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.1.2.tgz", - "integrity": "sha512-S6ji+flMEga+1QU79NDbwZ8Ivf0S/MpupQQiIC0rTpU/ZTKgcajijJJb1OcByBQDjrXCN1/DJtGz4ZJeBMPGJw==", + "version": "4.0.7", + "resolved": "https://registry.npmjs.org/speech-rule-engine/-/speech-rule-engine-4.0.7.tgz", + "integrity": "sha512-sJrL3/wHzNwJRLBdf6CjJWIlxC04iYKkyXvYSVsWVOiC2DSkHmxsqOhEeMsBA9XK+CHuNcsdkbFDnoUfAsmp9g==", "requires": { - "@xmldom/xmldom": "0.9.8", - "commander": "13.1.0", - "wicked-good-xpath": "1.3.0" + "commander": "9.2.0", + "wicked-good-xpath": "1.3.0", + "xmldom-sre": "0.1.31" }, "dependencies": { "commander": { - "version": "13.1.0", - "resolved": "https://registry.npmjs.org/commander/-/commander-13.1.0.tgz", - "integrity": "sha512-/rFeCpNJQbhSZjGVwO9RFV3xPqbnERS8MmIQzCtD/zl6gpJuV/bMLuN92oG3F7d8oDEHHRrujSXNUr8fpjntKw==" + "version": "9.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.2.0.tgz", + "integrity": "sha512-e2i4wANQiSXgnrBlIatyHtP1odfUp0BbV5Y5nEGbxtIrStkEOAAzCUirvLBNXHLr7kwLvJl6V+4V3XV9x7Wd9w==" } } }, - "string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", "requires": { - "safe-buffer": "~5.1.0" + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "requires": { + "safe-buffer": "~5.2.0" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" } }, "strtok3": { - "version": "10.3.4", - "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-10.3.4.tgz", - "integrity": "sha512-KIy5nylvC5le1OdaaoCJ07L+8iQzJHGH6pWDuzS+d07Cu7n1MZ2x26P8ZKIWfbK02+XIL8Mp4RkWeqdUCrDMfg==", + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/strtok3/-/strtok3-6.3.0.tgz", + "integrity": "sha512-fZtbhtvI9I48xDSywd/somNqgUHl2L2cstmXCCif0itOf96jeW18MBSyrLuNicYQVkvpOxkZtkzujiTJ9LW5Jw==", "requires": { - "@tokenizer/token": "^0.3.0" + "@tokenizer/token": "^0.3.0", + "peek-readable": "^4.1.0" } }, "supports-color": { @@ -2633,15 +3030,16 @@ } }, "tar": { - "version": "7.5.11", - "resolved": "https://registry.npmjs.org/tar/-/tar-7.5.11.tgz", - "integrity": "sha512-ChjMH33/KetonMTAtpYdgUFr0tbz69Fp2v7zWxQfYZX4g5ZN2nOBXm1R2xyA+lMIKrLKIoKAwFj93jE/avX9cQ==", + "version": "6.2.1", + "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz", + "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==", "requires": { - "@isaacs/fs-minipass": "^4.0.0", - "chownr": "^3.0.0", - "minipass": "^7.1.2", - "minizlib": "^3.1.0", - "yallist": "^5.0.0" + "chownr": "^2.0.0", + "fs-minipass": "^2.0.0", + "minipass": "^5.0.0", + "minizlib": "^2.1.1", + "mkdirp": "^1.0.3", + "yallist": "^4.0.0" } }, "tar-stream": { @@ -2656,20 +3054,15 @@ "readable-stream": "^3.1.1" } }, - "textarea-caret": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/textarea-caret/-/textarea-caret-3.1.0.tgz", - "integrity": "sha512-cXAvzO9pP5CGa6NKx0WYHl+8CHKZs8byMkt3PCJBCmq2a34YA9pO1NrQET5pzeqnBjBdToF5No4rrmkDUgQC2Q==" - }, "tmp": { "version": "0.2.5", "resolved": "https://registry.npmjs.org/tmp/-/tmp-0.2.5.tgz", "integrity": "sha512-voyz6MApa1rQGUxT3E+BK7/ROe8itEx7vD8/HEvt4xwXucvQ5G5oeEiHkmHZJuBO21RpOf+YYm9MOivj709jow==" }, "to-buffer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", - "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.1.tgz", + "integrity": "sha512-tB82LpAIWjhLYbqjx3X4zEeHN6M8CiuOEy2JY8SEQVdYRe3CCHOFaqrBW1doLDrfpWhplcW7BL+bO3/6S3pcDQ==", "requires": { "isarray": "^2.0.5", "safe-buffer": "^5.2.1", @@ -2680,20 +3073,14 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==" - }, - "safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==" } } }, "token-types": { - "version": "6.1.2", - "resolved": "https://registry.npmjs.org/token-types/-/token-types-6.1.2.tgz", - "integrity": "sha512-dRXchy+C0IgK8WPC6xvCHFRIWYUbqqdEIKPaKo/AcTUNzwLTK6AH7RjdLWsEZcAN/TBdtfUw3PYEgPr5VPr6ww==", + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/token-types/-/token-types-4.2.1.tgz", + "integrity": "sha512-6udB24Q737UD/SDsKAHI9FCRP7Bqc9D/MQUV02ORQg5iskjtLJlZJNdN4kKtcdtwCeWIwIHDGaUsTsCCAa8sFQ==", "requires": { - "@borewit/text-codec": "^0.2.1", "@tokenizer/token": "^0.3.0", "ieee754": "^1.2.1" } @@ -2709,9 +3096,9 @@ "integrity": "sha512-iawgk0hLP3SxGKDfnDJf8wTz4p2qImnyihM5Hh/sGvQ3K37dPi/w8sRhdNIxYA1TwFwc5mDhIJq+O0RsvXBKdQ==" }, "tslib": { - "version": "2.8.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", - "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", + "integrity": "sha512-AEYxH93jGFPn/a2iVAwW87VuUIkR1FVUKB77NwMF7nBTDkDrrT/Hpt/IrCJ0QXhW27jTBDcf5ZY7w6RiqTMw2Q==" }, "type-detect": { "version": "4.0.8", @@ -2727,6 +3114,138 @@ "call-bound": "^1.0.3", "es-errors": "^1.3.0", "is-typed-array": "^1.1.14" + }, + "dependencies": { + "available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "requires": { + "possible-typed-array-names": "^1.0.0" + } + }, + "call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "requires": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + } + }, + "define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "requires": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + } + }, + "for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "requires": { + "is-callable": "^1.2.7" + } + }, + "get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "requires": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "dependencies": { + "es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==" + } + } + }, + "gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==" + }, + "has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "requires": { + "es-define-property": "^1.0.0" + } + }, + "has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==" + }, + "has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "requires": { + "has-symbols": "^1.0.3" + } + }, + "hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "requires": { + "function-bind": "^1.1.2" + } + }, + "is-typed-array": { + "version": "1.1.15", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", + "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", + "requires": { + "which-typed-array": "^1.1.16" + } + }, + "set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "requires": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + } + }, + "which-typed-array": { + "version": "1.1.19", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.19.tgz", + "integrity": "sha512-rEvr90Bck4WZt9HHFC4DJMsjvu7x+r6bImz0/BrbWb7A2djJ8hnZMrWnHo9F8ssv0OMErasDhftrfROTyqSDrw==", + "requires": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + } + } } }, "uc.micro": { @@ -2734,16 +3253,6 @@ "resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz", "integrity": "sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==" }, - "uint8array-extras": { - "version": "1.5.0", - "resolved": "https://registry.npmjs.org/uint8array-extras/-/uint8array-extras-1.5.0.tgz", - "integrity": "sha512-rvKSBiC5zqCCiDZ9kAOszZcDvdAHwwIKJG33Ykj43OKcWsnmcBRL09YTU4nOeHZ8Y2a7l1MgTd08SBe9A8Qj6A==" - }, - "undate": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/undate/-/undate-0.3.0.tgz", - "integrity": "sha512-ssH8QTNBY6B+2fRr3stSQ+9m2NT8qTaun3ExTx5ibzYQvP7yX4+BnX0McNxFCvh6S5ia/DYu6bsCKQx/U4nb/Q==" - }, "unzipper": { "version": "0.10.14", "resolved": "https://registry.npmjs.org/unzipper/-/unzipper-0.10.14.tgz", @@ -2774,9 +3283,30 @@ "string_decoder": "~1.1.1", "util-deprecate": "~1.0.1" } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==" + }, + "string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "requires": { + "safe-buffer": "~5.1.0" + } } } }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, "util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -2882,25 +3412,19 @@ "webidl-conversions": "^3.0.0" } }, - "which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", - "requires": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - } - }, "wicked-good-xpath": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/wicked-good-xpath/-/wicked-good-xpath-1.3.0.tgz", "integrity": "sha512-Gd9+TUn5nXdwj/hFsPVx5cuHHiF5Bwuc30jZ4+ronF1qHK5O7HD0sgmXWSEgwKquT3ClLoKPVbO6qGwVwLzvAw==" }, + "wide-align": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz", + "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==", + "requires": { + "string-width": "^1.0.2 || 2 || 3 || 4" + } + }, "wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", @@ -2911,10 +3435,15 @@ "resolved": "https://registry.npmjs.org/xmlchars/-/xmlchars-2.2.0.tgz", "integrity": "sha512-JZnDKK8B0RCDw84FNdDAIpZK+JuJw+s7Lz8nksI7SIuU3UXJJslUthsi+uWBUYOwPFwW7W7PRLRfUKpxjtjFCw==" }, + "xmldom-sre": { + "version": "0.1.31", + "resolved": "https://registry.npmjs.org/xmldom-sre/-/xmldom-sre-0.1.31.tgz", + "integrity": "sha512-f9s+fUkX04BxQf+7mMWAp5zk61pciie+fFLC9hX9UVvCeJQfNHRHXpeo5MPcR0EUf57PYLdt+ZO4f3Ipk2oZUw==" + }, "yallist": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/yallist/-/yallist-5.0.0.tgz", - "integrity": "sha512-YgvUTfwqyc7UXVMrB+SImsVYSmTS8X/tSrtdNZMImM+n7+QTriRXyXim0mBrTXNeqzVF0KWGgHPeiyViFFrNDw==" + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==" }, "zip-stream": { "version": "4.1.1", diff --git a/package.json b/package.json index 91a4ae595..7edd176d4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "wekan", - "version": "v8.35.0", + "version": "v8.16.0", "description": "Open-Source kanban", "private": true, "repository": { @@ -11,50 +11,46 @@ "bugs": { "url": "https://github.com/wekan/wekan/issues" }, - "homepage": "https://wekan.fi", + "homepage": "https://wekan.github.io", "devDependencies": { "flatted": "^3.3.1", - "sinon": "^21.0.1" + "sinon": "^13.0.2" }, "dependencies": { "@babel/runtime": "^7.28.4", - "@mapbox/node-pre-gyp": "^2.0.3", + "@mapbox/node-pre-gyp": "^1.0.10", "@meteorjs/reify": "^0.25.4", "@rwap/jquery-ui-touch-punch": "^1.0.11", - "@textcomplete/contenteditable": "^0.1.13", - "@textcomplete/core": "^0.1.13", - "@textcomplete/textarea": "^0.1.13", - "@wekanteam/dragscroll": "^0.0.9", - "@wekanteam/exceljs": "^4.5.1", + "@wekanteam/dragscroll": "https://github.com/wekan/dragscroll.git", + "@wekanteam/exceljs": "git+https://github.com/wekan/exceljs.git", "@wekanteam/html-to-markdown": "^1.0.2", - "@wekanteam/meteor-globals": "^1.1.6", - "@wekanteam/meteor-reactive-cache": "^1.0.8", - "ajv": "^8.18.0", + "@wekanteam/meteor-globals": "^1.1.4", + "@wekanteam/meteor-reactive-cache": "^1.0.6", + "ajv": "^6.12.6", "bcryptjs": "^2.4.3", "bson": "^4.7.2", "chart.js": "^4.5.0", - "dompurify": "^3.3.2", + "dompurify": "^3.2.7", "es6-promise": "^4.2.4", "escape-string-regexp": "^5.0.0", "fibers": "^5.0.3", + "file-type": "^16.5.4", "filesize": "^8.0.7", - "hotkeys-js": "^3.13.15", "i18next": "^21.10.0", "i18next-sprintf-postprocessor": "^0.2.2", "jquery": "^3.7.1", - "jquery-ui": "^1.14.2", + "jquery-ui": "^1.13.3", "jszip": "^3.7.1", "ldapjs": "^2.3.3", - "limax": "4.1.0", "markdown-it": "^12.3.2", "markdown-it-emoji": "^2.0.0", "markdown-it-mathjax3": "^4.3.2", "meteor-accounts-t9n": "^2.6.0", - "meteor-node-stubs": "^1.2.26", + "meteor-node-stubs": "^1.2.24", "os": "^0.1.2", "papaparse": "^5.5.3", "pretty-ms": "^7.0.1", - "qs": "^6.15.0", + "qs": "^6.13.0", "simpl-schema": "^3.4.6", "source-map-support": "^0.5.20", "to-buffer": "^1.2.1", diff --git a/packages/kadira-flow-router/.gitignore b/packages/kadira-flow-router/.gitignore new file mode 100644 index 000000000..22ee0ccee --- /dev/null +++ b/packages/kadira-flow-router/.gitignore @@ -0,0 +1,3 @@ +.build* +*.browserify.js.cached +*.browserify.js.map diff --git a/packages/kadira-flow-router/.travis.yml b/packages/kadira-flow-router/.travis.yml new file mode 100644 index 000000000..5125066a4 --- /dev/null +++ b/packages/kadira-flow-router/.travis.yml @@ -0,0 +1,8 @@ +sudo: required +language: node_js +node_js: + - "0.10" +before_install: + - "curl -L http://git.io/ejPSng | /bin/sh" +env: + - TEST_COMMAND=meteor \ No newline at end of file diff --git a/packages/kadira-flow-router/CHANGELOG.md b/packages/kadira-flow-router/CHANGELOG.md new file mode 100644 index 000000000..80fd67080 --- /dev/null +++ b/packages/kadira-flow-router/CHANGELOG.md @@ -0,0 +1,155 @@ +# Changelog + +### v2.12.1 + +* Add NPM modules back. Fixes: [#602](https://github.com/kadirahq/flow-router/issues/602) + +### v2.12.0 + +* Update Fast Render to v2.14.0 + +### v2.11.0 + +* Add support for Meteor 1.3 RC-1. +* Removes browserify and get modules from Meteor 1.3. + +### v2.10.1 +* Fix the url generation for prefixed paths. See: [#508](https://github.com/kadirahq/flow-router/issues/508) + +### v2.10.0 +* Update few dependencies to the latest versions: pagejs, qs, cosmos:browserify + +### v2.9.0 +* Add FlowRouter.url() See: [#374](https://github.com/kadirahq/flow-router/pull/374) + +### v2.8.0 +* Allow to access options in groups as well. See: [#378](https://github.com/kadirahq/flow-router/pull/378) + +### v2.7.0 +* Add Path Prefix support. See: [#329](https://github.com/kadirahq/flow-router/pull/329) + +### v2.6.2 +* Now .current() sends a cloned version of the internal current object. Which prevent outside mutations to params and queryParams + +### v2.6.1 + +* Fix [#143](https://github.com/kadirahq/flow-router/issues/314). + This says that when we are doing a trigger redirect, + We won't get reactive changes like: `getRouteName()` + +### v2.6.0 +* Add hashbang support. See [#311](https://github.com/kadirahq/flow-router/pull/311) + +### v2.5.0 +* Add a stop callback on the triggers. See: [#306](https://github.com/kadirahq/flow-router/pull/306). + +### v2.4.0 + +* Add a name to the route groups. See: [#290](https://github.com/kadirahq/flow-router/pull/290) + +### v2.3.0 +* We've used `path` for both the current path and for the pathDef earlier. Now we differentiate it. See: [#272](https://github.com/kadirahq/flow-router/issues/272) and [#273](https://github.com/kadirahq/flow-router/pull/273) for more information. + +### v2.2.0 +* Add the first addOn api: FlowRouter.onRouteRegister(cb) + +### v2.1.1 +* There was an issue in IE9 support. We fix it with this version. + +### v2.1.0 +* Add IE9 Support. See this issue [#111](https://github.com/kadirahq/flow-router/issues/111) for more info. + +### v2.0.2 + +* Add missing queryParams object in the subscriptions method (with FR on the server) +* With that, [#237](https://github.com/kadirahq/flow-router/issues/237) is partially fixed. + +### v2.0.1 + +* Use pagejs.redirect() for our redirection process. +* Above fixes [#239](https://github.com/kadirahq/flow-router/issues/239) + +### v2.0.0 + +* Released 2.0 :) +* Now flow-router comes as `kadira:flow-router` +* Remove deprecated APIs + - `FlowRouter.reactiveCurrent()` + - Middlewares + - `FlowRouter.current().params.query` +* Follow the [migration guide](https://github.com/kadirahq/flow-router#migrating-into-20) for more information. + +### v1.18.0 + +* Implement idempotent routing on withReplaceState. See: [#197](https://github.com/meteorhacks/flow-router/issues/197) +* Add an [API](https://github.com/meteorhacks/flow-router#flowrouterwithtrailingslashfn) to set trailing slashes. + +### v1.17.2 +* Fix [#182](https://github.com/meteorhacks/flow-router/issues/182) - Now trigger's redirect function support `FlowRouter.go()` syntax. + +### v1.17.1 + +* Fix [#164](https://github.com/meteorhacks/flow-router/issues/164) - It's an issue when using `check` with flow router query params. +* Fix [#168](https://github.com/meteorhacks/flow-router/pull/168) - It's URL encoding issue. + +### v1.17.0 + +* Add an API called `FlowRouter.wait()` to wait the initialization and pass it back to the app. Fixes issue [180](https://github.com/meteorhacks/flow-router/issues/180). + +### v1.16.3 + +* Fix a crazy context switching issue. For more information see commit [6ca54cc](https://github.com/meteorhacks/flow-router/commit/6ca54cc7969b3a8aa71d63c98c99a20b175125a2) + +### v1.16.2 +* Fix issue [#167](https://github.com/meteorhacks/flow-router/issues/167) via [#175](https://github.com/meteorhacks/flow-router/pull/175) +* Fix [#176](https://github.com/meteorhacks/flow-router/issues/176) by the removal of `Tracker.flush` usage. + +### v1.16.1 +* Fix [issue](https://github.com/meteorhacks/flow-router/pull/173) of overwriting global triggers when written multiple times. + +### v1.16.0 + +* [Refactor](https://github.com/meteorhacks/flow-router/pull/172) triggers API for clean code +* Added [redirect](https://github.com/meteorhacks/flow-router#redirecting-with-triggers) functionality for triggers +* Now we are API complete for the 2.x release + +### v1.15.0 + +* Now all our routes are idempotent. +* If some one needs to re-run the route, he needs to use our `FlowRouter.reload()` API. + +### v1.14.1 + +* Fix regression came from v1.11.0. With that, `FlowRouter.go("/")` does not work. More information on [#147](https://github.com/meteorhacks/flow-router/issues/147). + +### v1.14.0 +* Bring browserify back with the updated version of `cosmos:browserify` which fixes some size issues. See [more info](https://github.com/meteorhacks/flow-router/issues/128#issuecomment-109799953). + +### v1.13.0 +* Remove browserified pagejs and qs dependency loading. With that we could reduce ~10kb of data size (without compression). We can look for a bower integration in the future. For now, here are the dependencies we have. + - page@1.6.3: https://github.com/visionmedia/page.js + - qs@3.1.0: https://github.com/hapijs/qs + +### v1.12.0 +* Add [`FlowRouter.withReplaceState`](https://github.com/meteorhacks/flow-router#flowrouterwithreplcaestatefn) api to use replaceState when changing routes via FlowRouter apis. + +### v1.11.0 +* Fix [#145](https://github.com/meteorhacks/flow-router/issues/145) by changing how safeToRun works. +* Add `FlowRouter.path()` to the server side +* Fix [#130](https://github.com/meteorhacks/flow-router/issues/130) + +### v1.10.0 +Add support for [triggers](https://github.com/meteorhacks/flow-router#triggers). This is something similar to middlewares but not as middlewares. Visit [here](https://github.com/meteorhacks/flow-router/pull/59) to learn about design decisions. + +_**Now, middlewares are deprecated.**_ + +### v1.9.0 +Fix [#120](https://github.com/meteorhacks/flow-router/issues/120) and added callback support for `FlowRouter.subsReady()`. + +### v1.8.0 + +This release comes with improvements to the reactive API. + +* Fixed [#77](https://github.com/meteorhacks/flow-router/issues/77), [#85](https://github.com/meteorhacks/flow-router/issues/85), [#95](https://github.com/meteorhacks/flow-router/issues/95), [#96](https://github.com/meteorhacks/flow-router/issues/96), [#103](https://github.com/meteorhacks/flow-router/issues/103) +* Add a new API called `FlowRouter.watchPathChange()` +* Deprecated `FlowRouter.reactiveCurrent()` in the favour of `FlowRouter.watchPathChange()` diff --git a/packages/kadira-flow-router/CONTRIBUTING.md b/packages/kadira-flow-router/CONTRIBUTING.md new file mode 100644 index 000000000..adb08f1b7 --- /dev/null +++ b/packages/kadira-flow-router/CONTRIBUTING.md @@ -0,0 +1,16 @@ +## Whether to submit an issue or not? + +We've very limited time to answer all the issues and respond them in a proper manner. +So, this repo's issue list only used to report **bugs** and **new features.** + +For any other questions, issues or asking for best practices use [Meteor Forums](https://forums.meteor.com/). +Even before you ask a question on Meteor Forums, make sure you read the [Meteor Routing Guide](https://kadira.io/academy/meteor-routing-guide). + +## Implementing Feature and Bug Fixes + +We are welcome and greedy for PRs. So, + +* If you wanna fix a bug, simply submit it. +* If you wanna implement feature or support with contributions, just drop a message to arunoda [at] kadira.io. + + diff --git a/packages/kadira-flow-router/LICENSE b/packages/kadira-flow-router/LICENSE new file mode 100644 index 000000000..6519acbfd --- /dev/null +++ b/packages/kadira-flow-router/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2015 MeteorHacks Pvt Ltd (Sri Lanka). <hello@meteorhacks.com> + +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. \ No newline at end of file diff --git a/packages/kadira-flow-router/README.md b/packages/kadira-flow-router/README.md new file mode 100644 index 000000000..7e74eb100 --- /dev/null +++ b/packages/kadira-flow-router/README.md @@ -0,0 +1,777 @@ +# FlowRouter [![Build Status](https://travis-ci.org/kadirahq/flow-router.svg?branch=master)](https://travis-ci.org/kadirahq/flow-router) [![Stories in Ready](https://badge.waffle.io/kadirahq/flow-router.svg?label=doing&title=Activities)](http://waffle.io/kadirahq/flow-router) + +Forked for bug fixes + +Carefully Designed Client Side Router for Meteor. + +FlowRouter is a very simple router for Meteor. It does routing for client-side apps and does not handle rendering itself. + +It exposes a great API for changing the URL and reactively getting data from the URL. However, inside the router, it's not reactive. Most importantly, FlowRouter is designed with performance in mind and it focuses on what it does best: **routing**. + +> We've released 2.0 and follow this [migration guide](#migrating-into-20) if you are already using FlowRouter. + +## TOC + +* [Meteor Routing Guide](#meteor-routing-guide) +* [Getting Started](#getting-started) +* [Routes Definition](#routes-definition) +* [Group Routes](#group-routes) +* [Rendering and Layout Management](#rendering-and-layout-management) +* [Triggers](#triggers) +* [Not Found Routes](#not-found-routes) +* [API](#api) +* [Subscription Management](#subscription-management) +* [IE9 Support](#ie9-support) +* [Hashbang URLs](#hashbang-urls) +* [Prefixed paths](#prefixed-paths) +* [Add-ons](#add-ons) +* [Difference with Iron Router](#difference-with-iron-router) +* [Migrating into 2.0](#migrating-into-20) + +## Meteor Routing Guide + +[Meteor Routing Guide](https://kadira.io/academy/meteor-routing-guide) is a completed guide into **routing** and related topics in Meteor. It talks about how to use FlowRouter properly and use it with **Blaze and React**. It also shows how to manage **subscriptions** and implement **auth logic** in the view layer. + +[![Meteor Routing Guide](https://cldup.com/AxlPfoxXmR.png)](https://kadira.io/academy/meteor-routing-guide) + +## Getting Started + +Add FlowRouter to your app: + +~~~shell +meteor add kadira:flow-router +~~~ + +Let's write our first route (add this file to `lib/router.js`): + +~~~js +FlowRouter.route('/blog/:postId', { + action: function(params, queryParams) { + console.log("Yeah! We are on the post:", params.postId); + } +}); +~~~ + +Then visit `/blog/my-post-id` from the browser or invoke the following command from the browser console: + +~~~js +FlowRouter.go('/blog/my-post-id'); +~~~ + +Then you can see some messages printed in the console. + +## Routes Definition + +FlowRouter routes are very simple and based on the syntax of [path-to-regexp](https://github.com/pillarjs/path-to-regexp) which is used in both [Express](http://expressjs.com/) and `iron:router`. + +Here's the syntax for a simple route: + +~~~js +FlowRouter.route('/blog/:postId', { + // do some action for this route + action: function(params, queryParams) { + console.log("Params:", params); + console.log("Query Params:", queryParams); + }, + + name: "<name for the route>" // optional +}); +~~~ + +So, this route will be activated when you visit a url like below: + +~~~js +FlowRouter.go('/blog/my-post?comments=on&color=dark'); +~~~ + +After you've visit the route, this will be printed in the console: + +~~~ +Params: {postId: "my-post"} +Query Params: {comments: "on", color: "dark"} +~~~ + +For a single interaction, the router only runs once. That means, after you've visit a route, first it will call `triggers`, then `subscriptions` and finally `action`. After that happens, none of those methods will be called again for that route visit. + +You can define routes anywhere in the `client` directory. But, we recommend to add them in the `lib` directory. Then `fast-render` can detect subscriptions and send them for you (we'll talk about this is a moment). + +### Group Routes + +You can group routes for better route organization. Here's an example: + +~~~js +var adminRoutes = FlowRouter.group({ + prefix: '/admin', + name: 'admin', + triggersEnter: [function(context, redirect) { + console.log('running group triggers'); + }] +}); + +// handling /admin route +adminRoutes.route('/', { + action: function() { + BlazeLayout.render('componentLayout', {content: 'admin'}); + }, + triggersEnter: [function(context, redirect) { + console.log('running /admin trigger'); + }] +}); + +// handling /admin/posts +adminRoutes.route('/posts', { + action: function() { + BlazeLayout.render('componentLayout', {content: 'posts'}); + } +}); +~~~ + +**All of the options for the `FlowRouter.group()` are optional.** + +You can even have nested group routes as shown below: + +~~~js +var adminRoutes = FlowRouter.group({ + prefix: "/admin", + name: "admin" +}); + +var superAdminRoutes = adminRoutes.group({ + prefix: "/super", + name: "superadmin" +}); + +// handling /admin/super/post +superAdminRoutes.route('/post', { + action: function() { + + } +}); +~~~ + +You can determine which group the current route is in using: + +~~~js +FlowRouter.current().route.group.name +~~~ + +This can be useful for determining if the current route is in a specific group (e.g. *admin*, *public*, *loggedIn*) without needing to use prefixes if you don't want to. If it's a nested group, you can get the parent group's name with: + +~~~js +FlowRouter.current().route.group.parent.name +~~~ + +As with all current route properties, these are not reactive, but can be combined with `FlowRouter.watchPathChange()` to get group names reactively. + +## Rendering and Layout Management + +FlowRouter does not handle rendering or layout management. For that, you can use: + + * [Blaze Layout for Blaze](https://github.com/kadirahq/blaze-layout) + * [React Layout for React](https://github.com/kadirahq/meteor-react-layout) + +Then you can invoke the layout manager inside the `action` method in the router. + +~~~js +FlowRouter.route('/blog/:postId', { + action: function(params) { + BlazeLayout.render("mainLayout", {area: "blog"}); + } +}); +~~~ + +## Triggers + +Triggers are the way FlowRouter allows you to perform tasks before you **enter** into a route and after you **exit** from a route. + +#### Defining triggers for a route + +Here's how you can define triggers for a route: + +~~~js +FlowRouter.route('/home', { + // calls just before the action + triggersEnter: [trackRouteEntry], + action: function() { + // do something you like + }, + // calls when we decide to move to another route + // but calls before the next route started + triggersExit: [trackRouteClose] +}); + +function trackRouteEntry(context) { + // context is the output of `FlowRouter.current()` + Mixpanel.track("visit-to-home", context.queryParams); +} + +function trackRouteClose(context) { + Mixpanel.track("move-from-home", context.queryParams); +} +~~~ + +#### Defining triggers for a group route + +This is how you can define triggers on a group definition. + +~~~js +var adminRoutes = FlowRouter.group({ + prefix: '/admin', + triggersEnter: [trackRouteEntry], + triggersExit: [trackRouteEntry] +}); +~~~ + +> You can add triggers to individual routes in the group too. + +#### Defining Triggers Globally + +You can also define triggers globally. Here's how to do it: + +~~~js +FlowRouter.triggers.enter([cb1, cb2]); +FlowRouter.triggers.exit([cb1, cb2]); + +// filtering +FlowRouter.triggers.enter([trackRouteEntry], {only: ["home"]}); +FlowRouter.triggers.exit([trackRouteExit], {except: ["home"]}); +~~~ + +As you can see from the last two examples, you can filter routes using the `only` or `except` keywords. But, you can't use both `only` and `except` at once. + +> If you'd like to learn more about triggers and design decisions, visit [here](https://github.com/meteorhacks/flow-router/pull/59). + +#### Redirecting With Triggers + +You can redirect to a different route using triggers. You can do it from both enter and exit triggers. See how to do it: + +~~~js +FlowRouter.route('/', { + triggersEnter: [function(context, redirect) { + redirect('/some-other-path'); + }], + action: function(_params) { + throw new Error("this should not get called"); + } +}); +~~~ + +Every trigger callback comes with a second argument: a function you can use to redirect to a different route. Redirect also has few properties to make sure it's not blocking the router. + +* redirect must be called with an URL +* redirect must be called within the same event loop cycle (no async or called inside a Tracker) +* redirect cannot be called multiple times + +Check this [PR](https://github.com/meteorhacks/flow-router/pull/172) to learn more about our redirect API. + +#### Stopping the Callback With Triggers + +In some cases, you may need to stop the route callback from firing using triggers. You can do this in **before** triggers, using the third argument: the `stop` function. For example, you can check the prefix and if it fails, show the notFound layout and stop before the action fires. + +```js +var localeGroup = FlowRouter.group({ + prefix: '/:locale?', + triggersEnter: [localeCheck] +}); + +localeGroup.route('/login', { + action: function (params, queryParams) { + BlazeLayout.render('componentLayout', {content: 'login'}); + } +}); + +function localeCheck(context, redirect, stop) { + var locale = context.params.locale; + + if (locale !== undefined && locale !== 'fr') { + BlazeLayout.render('notFound'); + stop(); + } +} +``` + +> **Note**: When using the stop function, you should always pass the second **redirect** argument, even if you won't use it. + +## Not Found Routes + +You can configure Not Found routes like this: + +~~~js +FlowRouter.notFound = { + // Subscriptions registered here don't have Fast Render support. + subscriptions: function() { + + }, + action: function() { + + } +}; +~~~ + +## API + +FlowRouter has a rich API to help you to navigate the router and reactively get information from the router. + +#### FlowRouter.getParam(paramName); + +Reactive function which you can use to get a parameter from the URL. + +~~~js +// route def: /apps/:appId +// url: /apps/this-is-my-app + +var appId = FlowRouter.getParam("appId"); +console.log(appId); // prints "this-is-my-app" +~~~ + +#### FlowRouter.getQueryParam(queryStringKey); + +Reactive function which you can use to get a value from the queryString. + +~~~js +// route def: /apps/:appId +// url: /apps/this-is-my-app?show=yes&color=red + +var color = FlowRouter.getQueryParam("color"); +console.log(color); // prints "red" +~~~ + +#### FlowRouter.path(pathDef, params, queryParams) + +Generate a path from a path definition. Both `params` and `queryParams` are optional. + +Special characters in `params` and `queryParams` will be URL encoded. + +~~~js +var pathDef = "/blog/:cat/:id"; +var params = {cat: "met eor", id: "abc"}; +var queryParams = {show: "y+e=s", color: "black"}; + +var path = FlowRouter.path(pathDef, params, queryParams); +console.log(path); // prints "/blog/met%20eor/abc?show=y%2Be%3Ds&color=black" +~~~ + +If there are no params or queryParams, this will simply return the pathDef as it is. + +##### Using Route name instead of the pathDef + +You can also use the route's name instead of the pathDef. Then, FlowRouter will pick the pathDef from the given route. See the following example: + +~~~js +FlowRouter.route("/blog/:cat/:id", { + name: "blogPostRoute", + action: function(params) { + //... + } +}) + +var params = {cat: "meteor", id: "abc"}; +var queryParams = {show: "yes", color: "black"}; + +var path = FlowRouter.path("blogPostRoute", params, queryParams); +console.log(path); // prints "/blog/meteor/abc?show=yes&color=black" +~~~ + +#### FlowRouter.go(pathDef, params, queryParams); + +This will get the path via `FlowRouter.path` based on the arguments and re-route to that path. + +You can call `FlowRouter.go` like this as well: + +~~~js +FlowRouter.go("/blog"); +~~~ + + +#### FlowRouter.url(pathDef, params, queryParams) + +Just like `FlowRouter.path`, but gives the absolute url. (Uses `Meteor.absoluteUrl` behind the scenes.) + +#### FlowRouter.setParams(newParams) + +This will change the current params with the newParams and re-route to the new path. + +~~~js +// route def: /apps/:appId +// url: /apps/this-is-my-app?show=yes&color=red + +FlowRouter.setParams({appId: "new-id"}); +// Then the user will be redirected to the following path +// /apps/new-id?show=yes&color=red +~~~ + +#### FlowRouter.setQueryParams(newQueryParams) + +Just like `FlowRouter.setParams`, but for queryString params. + +To remove a query param set it to `null` like below: + +~~~js +FlowRouter.setQueryParams({paramToRemove: null}); +~~~ + +#### FlowRouter.getRouteName() + +To get the name of the route reactively. + +~~~js +Tracker.autorun(function() { + var routeName = FlowRouter.getRouteName(); + console.log("Current route name is: ", routeName); +}); +~~~ + +#### FlowRouter.current() + +Get the current state of the router. **This API is not reactive**. +If you need to watch the changes in the path simply use `FlowRouter.watchPathChange()`. + +This gives an object like this: + +~~~js +// route def: /apps/:appId +// url: /apps/this-is-my-app?show=yes&color=red + +var current = FlowRouter.current(); +console.log(current); + +// prints following object +// { +// path: "/apps/this-is-my-app?show=yes&color=red", +// params: {appId: "this-is-my-app"}, +// queryParams: {show: "yes", color: "red"} +// route: {pathDef: "/apps/:appId", name: "name-of-the-route"} +// } +~~~ + +#### FlowRouter.watchPathChange() + +Reactively watch the changes in the path. If you need to simply get the params or queryParams use dedicated APIs like `FlowRouter.getQueryParam()`. + +~~~js +Tracker.autorun(function() { + FlowRouter.watchPathChange(); + var currentContext = FlowRouter.current(); + // do anything with the current context + // or anything you wish +}); +~~~ + +#### FlowRouter.withReplaceState(fn) +Normally, all the route changes made via APIs like `FlowRouter.go` and `FlowRouter.setParams()` add a URL item to the browser history. For example, run the following code: + +~~~js +FlowRouter.setParams({id: "the-id-1"}); +FlowRouter.setParams({id: "the-id-2"}); +FlowRouter.setParams({id: "the-id-3"}); +~~~ + +Now you can hit the back button of your browser two times. This is normal behavior since users may click the back button and expect to see the previous state of the app. + +But sometimes, this is not something you want. You don't need to pollute the browser history. Then, you can use the following syntax. + +~~~js +FlowRouter.withReplaceState(function() { + FlowRouter.setParams({id: "the-id-1"}); + FlowRouter.setParams({id: "the-id-2"}); + FlowRouter.setParams({id: "the-id-3"}); +}); +~~~ + +Now, there is no item in the browser history. Just like `FlowRouter.setParams`, you can use any FlowRouter API inside `FlowRouter.withReplaceState`. + +> We named this function as `withReplaceState` because, replaceState is the underline API used for this functionality. Read more about [replace state & the history API](https://developer.mozilla.org/en-US/docs/Web/Guide/API/DOM/Manipulating_the_browser_history). + +#### FlowRouter.reload() + +FlowRouter routes are idempotent. That means, even if you call `FlowRouter.go()` to the same URL multiple times, it only activates in the first run. This is also true for directly clicking on paths. + +So, if you really need to reload the route, this is the API you want. + +#### FlowRouter.wait() and FlowRouter.initialize() + +By default, FlowRouter initializes the routing process in a `Meteor.startup()` callback. This works for most of the apps. But, some apps have custom initializations and FlowRouter needs to initialize after that. + +So, that's where `FlowRouter.wait()` comes to save you. You need to call it directly inside your JavaScript file. After that, whenever your app is ready call `FlowRouter.initialize()`. + +eg:- + +~~~js +// file: app.js +FlowRouter.wait(); +WhenEverYourAppIsReady(function() { + FlowRouter.initialize(); +}); +~~~ + +For more information visit [issue #180](https://github.com/meteorhacks/flow-router/issues/180). + +#### FlowRouter.onRouteRegister(cb) + +This API is specially designed for add-on developers. They can listen for any registered route and add custom functionality to FlowRouter. This works on both server and client alike. + +~~~js +FlowRouter.onRouteRegister(function(route) { + // do anything with the route object + console.log(route); +}); +~~~ + +Let's say a user defined a route like this: + +~~~js +FlowRouter.route('/blog/:post', { + name: 'postList', + triggersEnter: [function() {}], + subscriptions: function() {}, + action: function() {}, + triggersExit: [function() {}], + customField: 'customName' +}); +~~~ + +Then the route object will be something like this: + +~~~js +{ + pathDef: '/blog/:post', + name: 'postList', + options: {customField: 'customName'} +} +~~~ + +So, it's not the internal route object we are using. + +## Subscription Management + +For Subscription Management, we highly suggest you to follow [Template/Component level subscriptions](https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management). Visit this [guide](https://kadira.io/academy/meteor-routing-guide/content/subscriptions-and-data-management) for that. + +FlowRouter also has it's own subscription registration mechanism. We will remove this in version 3.0. We don't remove or deprecate it in version 2.x because this is the easiest way to implement FastRender support for your app. In 3.0 we've better support for FastRender with Server Side Rendering. + +FlowRouter only deals with registration of subscriptions. It does not wait until subscription becomes ready. This is how to register a subscription. + +~~~js +FlowRouter.route('/blog/:postId', { + subscriptions: function(params, queryParams) { + this.register('myPost', Meteor.subscribe('blogPost', params.postId)); + } +}); +~~~ + +We can also register global subscriptions like this: + +~~~js +FlowRouter.subscriptions = function() { + this.register('myCourses', Meteor.subscribe('courses')); +}; +~~~ + +All these global subscriptions run on every route. So, pay special attention to names when registering subscriptions. + +After you've registered your subscriptions, you can reactively check for the status of those subscriptions like this: + +~~~js +Tracker.autorun(function() { + console.log("Is myPost ready?:", FlowRouter.subsReady("myPost")); + console.log("Are all subscriptions ready?:", FlowRouter.subsReady()); +}); +~~~ + +So, you can use `FlowRouter.subsReady` inside template helpers to show the loading status and act accordingly. + +### FlowRouter.subsReady() with a callback + +Sometimes, we need to use `FlowRouter.subsReady()` in places where an autorun is not available. One such example is inside an event handler. For such places, we can use the callback API of `FlowRouter.subsReady()`. + +~~~js +Template.myTemplate.events({ + "click #id": function(){ + FlowRouter.subsReady("myPost", function() { + // do something + }); + } +}); +~~~ + +> Arunoda has discussed more about Subscription Management in FlowRouter in [this](https://meteorhacks.com/flow-router-and-subscription-management.html#subscription-management) blog post about [FlowRouter and Subscription Management](https://meteorhacks.com/flow-router-and-subscription-management.html). + +> He's showing how to build an app like this: + +>![FlowRouter's Subscription Management](https://cldup.com/esLzM8cjEL.gif) + +#### Fast Render +FlowRouter has built in support for [Fast Render](https://github.com/meteorhacks/fast-render). + +- `meteor add meteorhacks:fast-render` +- Put `router.js` in a shared location. We suggest `lib/router.js`. + +You can exclude Fast Render support by wrapping the subscription registration in an `isClient` block: + +~~~js +FlowRouter.route('/blog/:postId', { + subscriptions: function(params, queryParams) { + // using Fast Render + this.register('myPost', Meteor.subscribe('blogPost', params.postId)); + + // not using Fast Render + if(Meteor.isClient) { + this.register('data', Meteor.subscribe('bootstrap-data'); + } + } +}); +~~~ + +#### Subscription Caching + +You can also use [Subs Manager](https://github.com/meteorhacks/subs-manager) for caching subscriptions on the client. We haven't done anything special to make it work. It should work as it works with other routers. + +## IE9 Support + +FlowRouter has IE9 support. But it does not ship the **HTML5 history polyfill** out of the box. That's because most apps do not require it. + +If you need to support IE9, add the **HTML5 history polyfill** with the following package. + +~~~shell +meteor add tomwasd:history-polyfill +~~~ + +## Hashbang URLs + +To enable hashbang urls like `mydomain.com/#!/mypath` simple set the `hashbang` option to `true` in the initialize function: + +~~~js +// file: app.js +FlowRouter.wait(); +WhenEverYourAppIsReady(function() { + FlowRouter.initialize({hashbang: true}); +}); +~~~ + +## Prefixed paths + +In cases you wish to run multiple web application on the same domain name, you’ll probably want to serve your particular meteor application under a sub-path (eg `example.com/myapp`). In this case simply include the path prefix in the meteor `ROOT_URL` environment variable and FlowRouter will handle it transparently without any additional configuration. + +## Add-ons + +Router is a base package for an app. Other projects like [useraccounts](http://useraccounts.meteor.com/) should have support for FlowRouter. Otherwise, it's hard to use FlowRouter in a real project. Now a lot of packages have [started to support FlowRouter](https://kadira.io/blog/meteor/addon-packages-for-flowrouter). + +So, you can use your your favorite package with FlowRouter as well. If not, there is an [easy process](https://kadira.io/blog/meteor/addon-packages-for-flowrouter#what-if-project-xxx-still-doesn-t-support-flowrouter-) to convert them to FlowRouter. + +**Add-on API** + +We have also released a [new API](https://github.com/kadirahq/flow-router#flowrouteronrouteregistercb) to support add-on developers. With that add-on packages can get a notification, when the user created a route in their app. + +If you've more ideas for the add-on API, [let us know](https://github.com/kadirahq/flow-router/issues). + +## Difference with Iron Router + +FlowRouter and Iron Router are two different routers. Iron Router tries to be a full featured solution. It tries to do everything including routing, subscriptions, rendering and layout management. + +FlowRouter is a minimalistic solution focused on routing with UI performance in mind. It exposes APIs for related functionality. + +Let's learn more about the differences: + +### Rendering + +FlowRouter doesn't handle rendering. By decoupling rendering from the router it's possible to use any rendering framework, such as [Blaze Layout](https://github.com/kadirahq/blaze-layout) to render with Blaze's Dynamic Templates. Rendering calls are made in the the route's action. We have a layout manager for [React](https://github.com/kadirahq/meteor-react-layout) as well. + +### Subscriptions + +With FlowRouter, we highly suggest using template/component layer subscriptions. But, if you need to do routing in the router layer, FlowRouter has [subscription registration](#subscription-management) mechanism. Even with that, FlowRouter never waits for the subscriptions and view layer to do it. + +### Reactive Content + +In Iron Router you can use reactive content inside the router, but any hook or method can re-run in an unpredictable manner. FlowRouter limits reactive data sources to a single run; when it is first called. + +We think that's the way to go. Router is just a user action. We can work with reactive content in the rendering layer. + +### router.current() is evil + +`Router.current()` is evil. Why? Let's look at following example. Imagine we have a route like this in our app: + +~~~ +/apps/:appId/:section +~~~ + +Now let's say, we need to get `appId` from the URL. Then we will do, something like this in Iron Router. + +~~~js +Templates['foo'].helpers({ + "someData": function() { + var appId = Router.current().params.appId; + return doSomething(appId); + } +}); +~~~ + +Let's say we changed `:section` in the route. Then the above helper also gets rerun. If we add a query param to the URL, it gets rerun. That's because `Router.current()` looks for changes in the route(or URL). But in any of above cases, `appId` didn't get changed. + +Because of this, a lot parts of our app get re-run and re-rendered. This creates unpredictable rendering behavior in our app. + +FlowRouter fixes this issue by providing the `Router.getParam()` API. See how to use it: + +~~~js +Templates['foo'].helpers({ + "someData": function() { + var appId = FlowRouter.getParam('appId'); + return doSomething(appId); + } +}); +~~~ + +### No data context + +FlowRouter does not have a data context. Data context has the same problem as reactive `.current()`. We believe, it'll possible to get data directly in the template (component) layer. + +### Built in Fast Render Support + +FlowRouter has built in [Fast Render](https://github.com/meteorhacks/fast-render) support. Just add Fast Render to your app and it'll work. Nothing to change in the router. + +For more information check [docs](#fast-render). + +### Server Side Routing + +FlowRouter is a client side router and it **does not** support server side routing at all. But `subscriptions` run on the server to enable Fast Render support. + +#### Reason behind that + +Meteor is not a traditional framework where you can send HTML directly from the server. Meteor needs to send a special set of HTML to the client initially. So, you can't directly send something to the client yourself. + +Also, in the server we need look for different things compared with the client. For example: + +* In the server we have to deal with headers. +* In the server we have to deal with methods like `GET`, `POST`, etc. +* In the server we have Cookies. + +So, it's better to use a dedicated server-side router like [`meteorhacks:picker`](https://github.com/meteorhacks/picker). It supports connect and express middlewares and has a very easy to use route syntax. + +### Server Side Rendering + +FlowRouter 3.0 will have server side rendering support. We've already started the initial version and check our [`ssr`](https://github.com/meteorhacks/flow-router/tree/ssr) branch for that. + +It's currently very usable and Kadira already using it for <https://kadira.io> + +### Better Initial Loading Support + +In Meteor, we have to wait until all the JS and other resources send before rendering anything. This is an issue. In 3.0, with the support from Server Side Rendering we are going to fix it. + +## Migrating into 2.0 + +Migrating into version 2.0 is easy and you don't need to change any application code since you are already using 2.0 features and the APIs. In 2.0, we've changed names and removed some deprecated APIs. + +Here are the steps to migrate your app into 2.0. + +#### Use the New FlowRouter Package +* Now FlowRouter comes as `kadira:flow-router` +* So, remove `meteorhacks:flow-router` with : `meteor remove meteorhacks:flow-router` +* Then, add `kadira:flow-router` with `meteor add kadira:flow-router` + +#### Change FlowLayout into BlazeLayout +* We've also renamed FlowLayout as [BlazeLayout](https://github.com/kadirahq/blaze-layout). +* So, remove `meteorhacks:flow-layout` and add `kadira:blaze-layout` instead. +* You need to use `BlazeLayout.render()` instead of `FlowLayout.render()` + +#### Stop using deprecated Apis +* There is no middleware support. Use triggers instead. +* There is no API called `.reactiveCurrent()`, use `.watchPathChange()` instead. +* Earlier, you can access query params with `FlowRouter.current().params.query`. But, now you can't do that. Use `FlowRouter.current().queryParams` instead. diff --git a/packages/kadira-flow-router/client/_init.js b/packages/kadira-flow-router/client/_init.js new file mode 100644 index 000000000..a18fdc897 --- /dev/null +++ b/packages/kadira-flow-router/client/_init.js @@ -0,0 +1,11 @@ +// Export Router Instance +FlowRouter = new Router(); +FlowRouter.Router = Router; +FlowRouter.Route = Route; + +// Initialize FlowRouter +Meteor.startup(function () { + if(!FlowRouter._askedToWait) { + FlowRouter.initialize(); + } +}); diff --git a/packages/kadira-flow-router/client/group.js b/packages/kadira-flow-router/client/group.js new file mode 100644 index 000000000..b93296bc2 --- /dev/null +++ b/packages/kadira-flow-router/client/group.js @@ -0,0 +1,57 @@ +Group = function(router, options, parent) { + options = options || {}; + + if (options.prefix && !/^\/.*/.test(options.prefix)) { + var message = "group's prefix must start with '/'"; + throw new Error(message); + } + + this._router = router; + this.prefix = options.prefix || ''; + this.name = options.name; + this.options = options; + + this._triggersEnter = options.triggersEnter || []; + this._triggersExit = options.triggersExit || []; + this._subscriptions = options.subscriptions || Function.prototype; + + this.parent = parent; + if (this.parent) { + this.prefix = parent.prefix + this.prefix; + + this._triggersEnter = parent._triggersEnter.concat(this._triggersEnter); + this._triggersExit = this._triggersExit.concat(parent._triggersExit); + } +}; + +Group.prototype.route = function(pathDef, options, group) { + options = options || {}; + + if (!/^\/.*/.test(pathDef)) { + var message = "route's path must start with '/'"; + throw new Error(message); + } + + group = group || this; + pathDef = this.prefix + pathDef; + + var triggersEnter = options.triggersEnter || []; + options.triggersEnter = this._triggersEnter.concat(triggersEnter); + + var triggersExit = options.triggersExit || []; + options.triggersExit = triggersExit.concat(this._triggersExit); + + return this._router.route(pathDef, options, group); +}; + +Group.prototype.group = function(options) { + return new Group(this._router, options, this); +}; + +Group.prototype.callSubscriptions = function(current) { + if (this.parent) { + this.parent.callSubscriptions(current); + } + + this._subscriptions.call(current.route, current.params, current.queryParams); +}; diff --git a/packages/kadira-flow-router/client/modules.js b/packages/kadira-flow-router/client/modules.js new file mode 100644 index 000000000..7b734f449 --- /dev/null +++ b/packages/kadira-flow-router/client/modules.js @@ -0,0 +1,2 @@ +page = require('page'); +qs = require('qs'); diff --git a/packages/kadira-flow-router/client/route.js b/packages/kadira-flow-router/client/route.js new file mode 100644 index 000000000..b82e97213 --- /dev/null +++ b/packages/kadira-flow-router/client/route.js @@ -0,0 +1,125 @@ +Route = function(router, pathDef, options, group) { + options = options || {}; + + this.options = options; + this.pathDef = pathDef + + // Route.path is deprecated and will be removed in 3.0 + this.path = pathDef; + + if (options.name) { + this.name = options.name; + } + + this._action = options.action || Function.prototype; + this._subscriptions = options.subscriptions || Function.prototype; + this._triggersEnter = options.triggersEnter || []; + this._triggersExit = options.triggersExit || []; + this._subsMap = {}; + this._router = router; + + this._params = new ReactiveDict(); + this._queryParams = new ReactiveDict(); + this._routeCloseDep = new Tracker.Dependency(); + + // tracks the changes in the URL + this._pathChangeDep = new Tracker.Dependency(); + + this.group = group; +}; + +Route.prototype.clearSubscriptions = function() { + this._subsMap = {}; +}; + +Route.prototype.register = function(name, sub, options) { + this._subsMap[name] = sub; +}; + + +Route.prototype.getSubscription = function(name) { + return this._subsMap[name]; +}; + + +Route.prototype.getAllSubscriptions = function() { + return this._subsMap; +}; + +Route.prototype.callAction = function(current) { + var self = this; + self._action(current.params, current.queryParams); +}; + +Route.prototype.callSubscriptions = function(current) { + this.clearSubscriptions(); + if (this.group) { + this.group.callSubscriptions(current); + } + + this._subscriptions(current.params, current.queryParams); +}; + +Route.prototype.getRouteName = function() { + this._routeCloseDep.depend(); + return this.name; +}; + +Route.prototype.getParam = function(key) { + this._routeCloseDep.depend(); + return this._params.get(key); +}; + +Route.prototype.getQueryParam = function(key) { + this._routeCloseDep.depend(); + return this._queryParams.get(key); +}; + +Route.prototype.watchPathChange = function() { + this._pathChangeDep.depend(); +}; + +Route.prototype.registerRouteClose = function() { + this._params = new ReactiveDict(); + this._queryParams = new ReactiveDict(); + this._routeCloseDep.changed(); + this._pathChangeDep.changed(); +}; + +Route.prototype.registerRouteChange = function(currentContext, routeChanging) { + // register params + var params = currentContext.params; + this._updateReactiveDict(this._params, params); + + // register query params + var queryParams = currentContext.queryParams; + this._updateReactiveDict(this._queryParams, queryParams); + + // if the route is changing, we need to defer triggering path changing + // if we did this, old route's path watchers will detect this + // Real issue is, above watcher will get removed with the new route + // So, we don't need to trigger it now + // We are doing it on the route close event. So, if they exists they'll + // get notify that + if(!routeChanging) { + this._pathChangeDep.changed(); + } +}; + +Route.prototype._updateReactiveDict = function(dict, newValues) { + var currentKeys = _.keys(newValues); + var oldKeys = _.keys(dict.keyDeps); + + // set new values + // params is an array. So, _.each(params) does not works + // to iterate params + _.each(currentKeys, function(key) { + dict.set(key, newValues[key]); + }); + + // remove keys which does not exisits here + var removedKeys = _.difference(oldKeys, currentKeys); + _.each(removedKeys, function(key) { + dict.set(key, undefined); + }); +}; diff --git a/packages/kadira-flow-router/client/router.js b/packages/kadira-flow-router/client/router.js new file mode 100644 index 000000000..ae91751f2 --- /dev/null +++ b/packages/kadira-flow-router/client/router.js @@ -0,0 +1,587 @@ +Router = function () { + var self = this; + this.globals = []; + this.subscriptions = Function.prototype; + + this._tracker = this._buildTracker(); + this._current = {}; + + // tracks the current path change + this._onEveryPath = new Tracker.Dependency(); + + this._globalRoute = new Route(this); + + // holds onRoute callbacks + this._onRouteCallbacks = []; + + // if _askedToWait is true. We don't automatically start the router + // in Meteor.startup callback. (see client/_init.js) + // Instead user need to call `.initialize() + this._askedToWait = false; + this._initialized = false; + this._triggersEnter = []; + this._triggersExit = []; + this._routes = []; + this._routesMap = {}; + this._updateCallbacks(); + this.notFound = this.notfound = null; + // indicate it's okay (or not okay) to run the tracker + // when doing subscriptions + // using a number and increment it help us to support FlowRouter.go() + // and legitimate reruns inside tracker on the same event loop. + // this is a solution for #145 + this.safeToRun = 0; + + // Meteor exposes to the client the path prefix that was defined using the + // ROOT_URL environement variable on the server using the global runtime + // configuration. See #315. + this._basePath = __meteor_runtime_config__.ROOT_URL_PATH_PREFIX || ''; + + // this is a chain contains a list of old routes + // most of the time, there is only one old route + // but when it's the time for a trigger redirect we've a chain + this._oldRouteChain = []; + + this.env = { + replaceState: new Meteor.EnvironmentVariable(), + reload: new Meteor.EnvironmentVariable(), + trailingSlash: new Meteor.EnvironmentVariable() + }; + + // redirect function used inside triggers + this._redirectFn = function(pathDef, fields, queryParams) { + if (/^http(s)?:\/\//.test(pathDef)) { + var message = "Redirects to URLs outside of the app are not supported in this version of Flow Router. Use 'window.location = yourUrl' instead"; + throw new Error(message); + } + self.withReplaceState(function() { + var path = FlowRouter.path(pathDef, fields, queryParams); + self._page.redirect(path); + }); + }; + this._initTriggersAPI(); +}; + +Router.prototype.route = function(pathDef, options, group) { + if (!/^\/.*/.test(pathDef)) { + var message = "route's path must start with '/'"; + throw new Error(message); + } + + options = options || {}; + var self = this; + var route = new Route(this, pathDef, options, group); + + // calls when the page route being activates + route._actionHandle = function (context, next) { + var oldRoute = self._current.route; + self._oldRouteChain.push(oldRoute); + + var queryParams = self._qs.parse(context.querystring); + // _qs.parse() gives us a object without prototypes, + // created with Object.create(null) + // Meteor's check doesn't play nice with it. + // So, we need to fix it by cloning it. + // see more: https://github.com/meteorhacks/flow-router/issues/164 + queryParams = JSON.parse(JSON.stringify(queryParams)); + + self._current = { + path: context.path, + context: context, + params: context.params, + queryParams: queryParams, + route: route, + oldRoute: oldRoute + }; + + // we need to invalidate if all the triggers have been completed + // if not that means, we've been redirected to another path + // then we don't need to invalidate + var afterAllTriggersRan = function() { + self._invalidateTracker(); + }; + + var triggers = self._triggersEnter.concat(route._triggersEnter); + Triggers.runTriggers( + triggers, + self._current, + self._redirectFn, + afterAllTriggersRan + ); + }; + + // calls when you exit from the page js route + route._exitHandle = function(context, next) { + var triggers = self._triggersExit.concat(route._triggersExit); + Triggers.runTriggers( + triggers, + self._current, + self._redirectFn, + next + ); + }; + + this._routes.push(route); + if (options.name) { + this._routesMap[options.name] = route; + } + + this._updateCallbacks(); + this._triggerRouteRegister(route); + + return route; +}; + +Router.prototype.group = function(options) { + return new Group(this, options); +}; + +Router.prototype.path = function(pathDef, fields, queryParams) { + if (this._routesMap[pathDef]) { + pathDef = this._routesMap[pathDef].pathDef; + } + + var path = ""; + + // Prefix the path with the router global prefix + if (this._basePath) { + path += "/" + this._basePath + "/"; + } + + fields = fields || {}; + var regExp = /(:[\w\(\)\\\+\*\.\?]+)+/g; + path += pathDef.replace(regExp, function(key) { + var firstRegexpChar = key.indexOf("("); + // get the content behind : and (\\d+/) + key = key.substring(1, (firstRegexpChar > 0)? firstRegexpChar: undefined); + // remove +?* + key = key.replace(/[\+\*\?]+/g, ""); + + // this is to allow page js to keep the custom characters as it is + // we need to encode 2 times otherwise "/" char does not work properly + // So, in that case, when I includes "/" it will think it's a part of the + // route. encoding 2times fixes it + return encodeURIComponent(encodeURIComponent(fields[key] || "")); + }); + + // Replace multiple slashes with single slash + path = path.replace(/\/\/+/g, "/"); + + // remove trailing slash + // but keep the root slash if it's the only one + path = path.match(/^\/{1}$/) ? path: path.replace(/\/$/, ""); + + // explictly asked to add a trailing slash + if(this.env.trailingSlash.get() && _.last(path) !== "/") { + path += "/"; + } + + var strQueryParams = this._qs.stringify(queryParams || {}); + if(strQueryParams) { + path += "?" + strQueryParams; + } + + return path; +}; + +Router.prototype.go = function(pathDef, fields, queryParams) { + var path = this.path(pathDef, fields, queryParams); + + var useReplaceState = this.env.replaceState.get(); + if(useReplaceState) { + this._page.replace(path); + } else { + this._page(path); + } +}; + +Router.prototype.reload = function() { + var self = this; + + self.env.reload.withValue(true, function() { + self._page.replace(self._current.path); + }); +}; + +Router.prototype.redirect = function(path) { + this._page.redirect(path); +}; + +Router.prototype.setParams = function(newParams) { + if(!this._current.route) {return false;} + + var pathDef = this._current.route.pathDef; + var existingParams = this._current.params; + var params = {}; + _.each(_.keys(existingParams), function(key) { + params[key] = existingParams[key]; + }); + + params = _.extend(params, newParams); + var queryParams = this._current.queryParams; + + this.go(pathDef, params, queryParams); + return true; +}; + +Router.prototype.setQueryParams = function(newParams) { + if(!this._current.route) {return false;} + + var queryParams = _.clone(this._current.queryParams); + _.extend(queryParams, newParams); + + for (var k in queryParams) { + if (queryParams[k] === null || queryParams[k] === undefined) { + delete queryParams[k]; + } + } + + var pathDef = this._current.route.pathDef; + var params = this._current.params; + this.go(pathDef, params, queryParams); + return true; +}; + +// .current is not reactive +// This is by design. use .getParam() instead +// If you really need to watch the path change, use .watchPathChange() +Router.prototype.current = function() { + // We can't trust outside, that's why we clone this + // Anyway, we can't clone the whole object since it has non-jsonable values + // That's why we clone what's really needed. + var current = _.clone(this._current); + current.queryParams = EJSON.clone(current.queryParams); + current.params = EJSON.clone(current.params); + return current; +}; + +// Implementing Reactive APIs +var reactiveApis = [ + 'getParam', 'getQueryParam', + 'getRouteName', 'watchPathChange' +]; +reactiveApis.forEach(function(api) { + Router.prototype[api] = function(arg1) { + // when this is calling, there may not be any route initiated + // so we need to handle it + var currentRoute = this._current.route; + if(!currentRoute) { + this._onEveryPath.depend(); + return; + } + + // currently, there is only one argument. If we've more let's add more args + // this is not clean code, but better in performance + return currentRoute[api].call(currentRoute, arg1); + }; +}); + +Router.prototype.subsReady = function() { + var callback = null; + var args = _.toArray(arguments); + + if (typeof _.last(args) === "function") { + callback = args.pop(); + } + + var currentRoute = this.current().route; + var globalRoute = this._globalRoute; + + // we need to depend for every route change and + // rerun subscriptions to check the ready state + this._onEveryPath.depend(); + + if(!currentRoute) { + return false; + } + + var subscriptions; + if(args.length === 0) { + subscriptions = _.values(globalRoute.getAllSubscriptions()); + subscriptions = subscriptions.concat(_.values(currentRoute.getAllSubscriptions())); + } else { + subscriptions = _.map(args, function(subName) { + return globalRoute.getSubscription(subName) || currentRoute.getSubscription(subName); + }); + } + + var isReady = function() { + var ready = _.every(subscriptions, function(sub) { + return sub && sub.ready(); + }); + + return ready; + }; + + if (callback) { + Tracker.autorun(function(c) { + if (isReady()) { + callback(); + c.stop(); + } + }); + } else { + return isReady(); + } +}; + +Router.prototype.withReplaceState = function(fn) { + return this.env.replaceState.withValue(true, fn); +}; + +Router.prototype.withTrailingSlash = function(fn) { + return this.env.trailingSlash.withValue(true, fn); +}; + +Router.prototype._notfoundRoute = function(context) { + this._current = { + path: context.path, + context: context, + params: [], + queryParams: {}, + }; + + // XXX this.notfound kept for backwards compatibility + this.notFound = this.notFound || this.notfound; + if(!this.notFound) { + console.error("There is no route for the path:", context.path); + return; + } + + this._current.route = new Route(this, "*", this.notFound); + this._invalidateTracker(); +}; + +Router.prototype.initialize = function(options) { + options = options || {}; + + if(this._initialized) { + throw new Error("FlowRouter is already initialized"); + } + + var self = this; + this._updateCallbacks(); + + // Implementing idempotent routing + // by overriding page.js`s "show" method. + // Why? + // It is impossible to bypass exit triggers, + // because they execute before the handler and + // can not know what the next path is, inside exit trigger. + // + // we need override both show, replace to make this work + // since we use redirect when we are talking about withReplaceState + _.each(['show', 'replace'], function(fnName) { + var original = self._page[fnName]; + self._page[fnName] = function(path, state, dispatch, push) { + var reload = self.env.reload.get(); + if (!reload && self._current.path === path) { + return; + } + + original.call(this, path, state, dispatch, push); + }; + }); + + // this is very ugly part of pagejs and it does decoding few times + // in unpredicatable manner. See #168 + // this is the default behaviour and we need keep it like that + // we are doing a hack. see .path() + this._page.base(this._basePath); + this._page({ + decodeURLComponents: true, + hashbang: !!options.hashbang + }); + + this._initialized = true; +}; + +Router.prototype._buildTracker = function() { + var self = this; + + // main autorun function + var tracker = Tracker.autorun(function () { + if(!self._current || !self._current.route) { + return; + } + + // see the definition of `this._processingContexts` + var currentContext = self._current; + var route = currentContext.route; + var path = currentContext.path; + + if(self.safeToRun === 0) { + var message = + "You can't use reactive data sources like Session" + + " inside the `.subscriptions` method!"; + throw new Error(message); + } + + // We need to run subscriptions inside a Tracker + // to stop subs when switching between routes + // But we don't need to run this tracker with + // other reactive changes inside the .subscription method + // We tackle this with the `safeToRun` variable + self._globalRoute.clearSubscriptions(); + self.subscriptions.call(self._globalRoute, path); + route.callSubscriptions(currentContext); + + // otherwise, computations inside action will trigger to re-run + // this computation. which we do not need. + Tracker.nonreactive(function() { + var isRouteChange = currentContext.oldRoute !== currentContext.route; + var isFirstRoute = !currentContext.oldRoute; + // first route is not a route change + if(isFirstRoute) { + isRouteChange = false; + } + + // Clear oldRouteChain just before calling the action + // We still need to get a copy of the oldestRoute first + // It's very important to get the oldest route and registerRouteClose() it + // See: https://github.com/kadirahq/flow-router/issues/314 + var oldestRoute = self._oldRouteChain[0]; + self._oldRouteChain = []; + + currentContext.route.registerRouteChange(currentContext, isRouteChange); + route.callAction(currentContext); + + Tracker.afterFlush(function() { + self._onEveryPath.changed(); + if(isRouteChange) { + // We need to trigger that route (definition itself) has changed. + // So, we need to re-run all the register callbacks to current route + // This is pretty important, otherwise tracker + // can't identify new route's items + + // We also need to afterFlush, otherwise this will re-run + // helpers on templates which are marked for destroying + if(oldestRoute) { + oldestRoute.registerRouteClose(); + } + } + }); + }); + + self.safeToRun--; + }); + + return tracker; +}; + +Router.prototype._invalidateTracker = function() { + var self = this; + this.safeToRun++; + this._tracker.invalidate(); + // After the invalidation we need to flush to make changes imediately + // otherwise, we have face some issues context mix-maches and so on. + // But there are some cases we can't flush. So we need to ready for that. + + // we clearly know, we can't flush inside an autorun + // this may leads some issues on flow-routing + // we may need to do some warning + if(!Tracker.currentComputation) { + // Still there are some cases where we can't flush + // eg:- when there is a flush currently + // But we've no public API or hacks to get that state + // So, this is the only solution + try { + Tracker.flush(); + } catch(ex) { + // only handling "while flushing" errors + if(!/Tracker\.flush while flushing/.test(ex.message)) { + return; + } + + // XXX: fix this with a proper solution by removing subscription mgt. + // from the router. Then we don't need to run invalidate using a tracker + + // this happens when we are trying to invoke a route change + // with inside a route chnage. (eg:- Template.onCreated) + // Since we use page.js and tracker, we don't have much control + // over this process. + // only solution is to defer route execution. + + // It's possible to have more than one path want to defer + // But, we only need to pick the last one. + // self._nextPath = self._current.path; + Meteor.defer(function() { + var path = self._nextPath; + if(!path) { + return; + } + + delete self._nextPath; + self.env.reload.withValue(true, function() { + self.go(path); + }); + }); + } + } +}; + +Router.prototype._updateCallbacks = function () { + var self = this; + + self._page.callbacks = []; + self._page.exits = []; + + _.each(self._routes, function(route) { + self._page(route.pathDef, route._actionHandle); + self._page.exit(route.pathDef, route._exitHandle); + }); + + self._page("*", function(context) { + self._notfoundRoute(context); + }); +}; + +Router.prototype._initTriggersAPI = function() { + var self = this; + this.triggers = { + enter: function(triggers, filter) { + triggers = Triggers.applyFilters(triggers, filter); + if(triggers.length) { + self._triggersEnter = self._triggersEnter.concat(triggers); + } + }, + + exit: function(triggers, filter) { + triggers = Triggers.applyFilters(triggers, filter); + if(triggers.length) { + self._triggersExit = self._triggersExit.concat(triggers); + } + } + }; +}; + +Router.prototype.wait = function() { + if(this._initialized) { + throw new Error("can't wait after FlowRouter has been initialized"); + } + + this._askedToWait = true; +}; + +Router.prototype.onRouteRegister = function(cb) { + this._onRouteCallbacks.push(cb); +}; + +Router.prototype._triggerRouteRegister = function(currentRoute) { + // We should only need to send a safe set of fields on the route + // object. + // This is not to hide what's inside the route object, but to show + // these are the public APIs + var routePublicApi = _.pick(currentRoute, 'name', 'pathDef', 'path'); + var omittingOptionFields = [ + 'triggersEnter', 'triggersExit', 'action', 'subscriptions', 'name' + ]; + routePublicApi.options = _.omit(currentRoute.options, omittingOptionFields); + + _.each(this._onRouteCallbacks, function(cb) { + cb(routePublicApi); + }); +}; + +Router.prototype._page = page; +Router.prototype._qs = qs; diff --git a/packages/kadira-flow-router/client/triggers.js b/packages/kadira-flow-router/client/triggers.js new file mode 100644 index 000000000..b1ae7197e --- /dev/null +++ b/packages/kadira-flow-router/client/triggers.js @@ -0,0 +1,116 @@ +// a set of utility functions for triggers + +Triggers = {}; + +// Apply filters for a set of triggers +// @triggers - a set of triggers +// @filter - filter with array fileds with `only` and `except` +// support only either `only` or `except`, but not both +Triggers.applyFilters = function(triggers, filter) { + if(!(triggers instanceof Array)) { + triggers = [triggers]; + } + + if(!filter) { + return triggers; + } + + if(filter.only && filter.except) { + throw new Error("Triggers don't support only and except filters at once"); + } + + if(filter.only && !(filter.only instanceof Array)) { + throw new Error("only filters needs to be an array"); + } + + if(filter.except && !(filter.except instanceof Array)) { + throw new Error("except filters needs to be an array"); + } + + if(filter.only) { + return Triggers.createRouteBoundTriggers(triggers, filter.only); + } + + if(filter.except) { + return Triggers.createRouteBoundTriggers(triggers, filter.except, true); + } + + throw new Error("Provided a filter but not supported"); +}; + +// create triggers by bounding them to a set of route names +// @triggers - a set of triggers +// @names - list of route names to be bound (trigger runs only for these names) +// @negate - negate the result (triggers won't run for above names) +Triggers.createRouteBoundTriggers = function(triggers, names, negate) { + var namesMap = {}; + _.each(names, function(name) { + namesMap[name] = true; + }); + + var filteredTriggers = _.map(triggers, function(originalTrigger) { + var modifiedTrigger = function(context, next) { + var routeName = context.route.name; + var matched = (namesMap[routeName])? 1: -1; + matched = (negate)? matched * -1 : matched; + + if(matched === 1) { + originalTrigger(context, next); + } + }; + return modifiedTrigger; + }); + + return filteredTriggers; +}; + +// run triggers and abort if redirected or callback stopped +// @triggers - a set of triggers +// @context - context we need to pass (it must have the route) +// @redirectFn - function which used to redirect +// @after - called after if only all the triggers runs +Triggers.runTriggers = function(triggers, context, redirectFn, after) { + var abort = false; + var inCurrentLoop = true; + var alreadyRedirected = false; + + for(var lc=0; lc<triggers.length; lc++) { + var trigger = triggers[lc]; + trigger(context, doRedirect, doStop); + + if(abort) { + return; + } + } + + // mark that, we've exceeds the currentEventloop for + // this set of triggers. + inCurrentLoop = false; + after(); + + function doRedirect(url, params, queryParams) { + if(alreadyRedirected) { + throw new Error("already redirected"); + } + + /* + // Commenting out, so that redirects work when not in sync. + // https://github.com/wekan/wekan/issues/4514 + if(!inCurrentLoop) { + throw new Error("redirect needs to be done in sync"); + } + */ + + if(!url) { + throw new Error("trigger redirect requires an URL"); + } + + abort = true; + alreadyRedirected = true; + redirectFn(url, params, queryParams); + } + + function doStop() { + abort = true; + } +}; diff --git a/packages/kadira-flow-router/lib/router.js b/packages/kadira-flow-router/lib/router.js new file mode 100644 index 000000000..c0c9abc6a --- /dev/null +++ b/packages/kadira-flow-router/lib/router.js @@ -0,0 +1,9 @@ +Router.prototype.url = function() { + // We need to remove the leading base path, or "/", as it will be inserted + // automatically by `Meteor.absoluteUrl` as documented in: + // http://docs.meteor.com/#/full/meteor_absoluteurl + var completePath = this.path.apply(this, arguments); + var basePath = this._basePath || '/'; + var pathWithoutBase = completePath.replace(new RegExp('^' + basePath + '\/|(\/)'), ''); + return Meteor.absoluteUrl(pathWithoutBase); +}; diff --git a/packages/kadira-flow-router/package.js b/packages/kadira-flow-router/package.js new file mode 100644 index 000000000..f9f272406 --- /dev/null +++ b/packages/kadira-flow-router/package.js @@ -0,0 +1,81 @@ +Package.describe({ + name: 'kadira:flow-router', + summary: 'Carefully Designed Client Side Router for Meteor, fixed by Serubin', + version: '2.12.1', + git: 'https://github.com/serubin/flow-router.git' +}); + +Npm.depends({ + // In order to support IE9, we had to fork pagejs and apply + // this PR: https://github.com/visionmedia/page.js/pull/288 + 'page':'https://github.com/kadirahq/page.js/archive/34ddf45ea8e4c37269ce3df456b44fc0efc595c6.tar.gz', + 'qs':'5.2.0' + }); + +Package.onUse(function(api) { + configure(api); + api.export('FlowRouter'); +}); + +Package.onTest(function(api) { + configure(api); + api.use('tinytest'); + api.use('check'); + api.use('mongo'); + api.use('http'); + api.use('random'); + api.use('meteorhacks:fast-render'); + api.use('meteorhacks:inject-data'); + api.use('tmeasday:html5-history-api'); + + api.addFiles('test/common/fast_render_route.js', ['client', 'server']); + + api.addFiles('test/client/_helpers.js', 'client'); + api.addFiles('test/server/_helpers.js', 'server'); + + api.addFiles('test/client/loader.spec.js', 'client'); + api.addFiles('test/client/route.reactivity.spec.js', 'client'); + api.addFiles('test/client/router.core.spec.js', 'client'); + api.addFiles('test/client/router.subs_ready.spec.js', 'client'); + api.addFiles('test/client/router.reactivity.spec.js', 'client'); + api.addFiles('test/client/group.spec.js', 'client'); + api.addFiles('test/client/trigger.spec.js', 'client'); + api.addFiles('test/client/triggers.js', 'client'); + + api.addFiles('test/server/plugins/fast_render.js', 'server'); + + api.addFiles('test/common/router.path.spec.js', ['client', 'server']); + api.addFiles('test/common/router.url.spec.js', ['client', 'server']); + api.addFiles('test/common/router.addons.spec.js', ['client', 'server']); + api.addFiles('test/common/route.spec.js', ['client', 'server']); + api.addFiles('test/common/group.spec.js', ['client', 'server']); +}); + +function configure(api) { + //api.versionsFrom('METEOR@1.3-rc.1'); + + api.use('underscore'); + api.use('tracker'); + api.use('reactive-dict'); + api.use('reactive-var'); + api.use('ejson'); + api.use('modules'); + + api.use('meteorhacks:fast-render@2.14.0', ['client', 'server'], {weak: true}); + + api.addFiles('client/modules.js', 'client'); + api.addFiles('client/triggers.js', 'client'); + api.addFiles('client/router.js', 'client'); + api.addFiles('client/group.js', 'client'); + api.addFiles('client/route.js', 'client'); + api.addFiles('client/_init.js', 'client'); + + api.addFiles('server/router.js', 'server'); + api.addFiles('server/group.js', 'server'); + api.addFiles('server/route.js', 'server'); + api.addFiles('server/_init.js', 'server'); + + api.addFiles('server/plugins/fast_render.js', 'server'); + + api.addFiles('lib/router.js', ['client', 'server']); +} diff --git a/packages/kadira-flow-router/server/_init.js b/packages/kadira-flow-router/server/_init.js new file mode 100644 index 000000000..cf128603a --- /dev/null +++ b/packages/kadira-flow-router/server/_init.js @@ -0,0 +1,4 @@ +// Export Router Instance +FlowRouter = new Router(); +FlowRouter.Router = Router; +FlowRouter.Route = Route; diff --git a/packages/kadira-flow-router/server/group.js b/packages/kadira-flow-router/server/group.js new file mode 100644 index 000000000..89a6d027d --- /dev/null +++ b/packages/kadira-flow-router/server/group.js @@ -0,0 +1,18 @@ +Group = function(router, options) { + options = options || {}; + this.prefix = options.prefix || ''; + this.options = options; + this._router = router; +}; + +Group.prototype.route = function(pathDef, options) { + pathDef = this.prefix + pathDef; + return this._router.route(pathDef, options); +}; + +Group.prototype.group = function(options) { + var group = new Group(this._router, options); + group.parent = this; + + return group; +}; diff --git a/packages/kadira-flow-router/server/plugins/fast_render.js b/packages/kadira-flow-router/server/plugins/fast_render.js new file mode 100644 index 000000000..1121a9247 --- /dev/null +++ b/packages/kadira-flow-router/server/plugins/fast_render.js @@ -0,0 +1,40 @@ +if(!Package['meteorhacks:fast-render']) { + return; +} + +FastRender = Package['meteorhacks:fast-render'].FastRender; + +// hack to run after eveything else on startup +Meteor.startup(function () { + Meteor.startup(function () { + setupFastRender(); + }); +}); + +function setupFastRender () { + _.each(FlowRouter._routes, function (route) { + FastRender.route(route.pathDef, function (routeParams, path) { + var self = this; + + // anyone using Meteor.subscribe for something else? + var original = Meteor.subscribe; + Meteor.subscribe = function () { + return _.toArray(arguments); + }; + + route._subsMap = {}; + FlowRouter.subscriptions.call(route, path); + if(route.subscriptions) { + var queryParams = routeParams.query; + var params = _.omit(routeParams, 'query'); + route.subscriptions(params, queryParams); + } + _.each(route._subsMap, function (args) { + self.subscribe.apply(self, args); + }); + + // restore Meteor.subscribe, ... on server side + Meteor.subscribe = original; + }); + }); +} diff --git a/packages/kadira-flow-router/server/route.js b/packages/kadira-flow-router/server/route.js new file mode 100644 index 000000000..dd2be1c7a --- /dev/null +++ b/packages/kadira-flow-router/server/route.js @@ -0,0 +1,28 @@ +Route = function(router, pathDef, options) { + options = options || {}; + this.options = options; + this.name = options.name; + this.pathDef = pathDef; + + // Route.path is deprecated and will be removed in 3.0 + this.path = pathDef; + + this.action = options.action || Function.prototype; + this.subscriptions = options.subscriptions || Function.prototype; + this._subsMap = {}; +}; + + +Route.prototype.register = function(name, sub, options) { + this._subsMap[name] = sub; +}; + + +Route.prototype.subscription = function(name) { + return this._subsMap[name]; +}; + + +Route.prototype.middleware = function(middleware) { + +}; diff --git a/packages/kadira-flow-router/server/router.js b/packages/kadira-flow-router/server/router.js new file mode 100644 index 000000000..f7a50aea5 --- /dev/null +++ b/packages/kadira-flow-router/server/router.js @@ -0,0 +1,146 @@ +var Qs = Npm.require('qs'); + +Router = function () { + this._routes = []; + this._routesMap = {}; + this.subscriptions = Function.prototype; + + // holds onRoute callbacks + this._onRouteCallbacks = []; +}; + +Router.prototype.route = function(pathDef, options) { + if (!/^\/.*/.test(pathDef)) { + var message = "route's path must start with '/'"; + throw new Error(message); + } + + options = options || {}; + var route = new Route(this, pathDef, options); + this._routes.push(route); + + if (options.name) { + this._routesMap[options.name] = route; + } + + this._triggerRouteRegister(route); + return route; +}; + +Router.prototype.group = function(options) { + return new Group(this, options); +}; + +Router.prototype.path = function(pathDef, fields, queryParams) { + if (this._routesMap[pathDef]) { + pathDef = this._routesMap[pathDef].path; + } + + fields = fields || {}; + var regExp = /(:[\w\(\)\\\+\*\.\?]+)+/g; + var path = pathDef.replace(regExp, function(key) { + var firstRegexpChar = key.indexOf("("); + // get the content behind : and (\\d+/) + key = key.substring(1, (firstRegexpChar > 0)? firstRegexpChar: undefined); + // remove +?* + key = key.replace(/[\+\*\?]+/g, ""); + + return fields[key] || ""; + }); + + path = path.replace(/\/\/+/g, "/"); // Replace multiple slashes with single slash + + // remove trailing slash + // but keep the root slash if it's the only one + path = path.match(/^\/{1}$/) ? path: path.replace(/\/$/, ""); + + var strQueryParams = Qs.stringify(queryParams || {}); + if(strQueryParams) { + path += "?" + strQueryParams; + } + + return path; +}; + +Router.prototype.onRouteRegister = function(cb) { + this._onRouteCallbacks.push(cb); +}; + +Router.prototype._triggerRouteRegister = function(currentRoute) { + // We should only need to send a safe set of fields on the route + // object. + // This is not to hide what's inside the route object, but to show + // these are the public APIs + var routePublicApi = _.pick(currentRoute, 'name', 'pathDef', 'path'); + var omittingOptionFields = [ + 'triggersEnter', 'triggersExit', 'action', 'subscriptions', 'name' + ]; + routePublicApi.options = _.omit(currentRoute.options, omittingOptionFields); + + _.each(this._onRouteCallbacks, function(cb) { + cb(routePublicApi); + }); +}; + + +Router.prototype.go = function() { + // client only +}; + + +Router.prototype.current = function() { + // client only +}; + + +Router.prototype.triggers = { + enter: function() { + // client only + }, + exit: function() { + // client only + } +}; + +Router.prototype.middleware = function() { + // client only +}; + + +Router.prototype.getState = function() { + // client only +}; + + +Router.prototype.getAllStates = function() { + // client only +}; + + +Router.prototype.setState = function() { + // client only +}; + + +Router.prototype.removeState = function() { + // client only +}; + + +Router.prototype.clearStates = function() { + // client only +}; + + +Router.prototype.ready = function() { + // client only +}; + + +Router.prototype.initialize = function() { + // client only +}; + +Router.prototype.wait = function() { + // client only +}; diff --git a/packages/kadira-flow-router/test/client/_helpers.js b/packages/kadira-flow-router/test/client/_helpers.js new file mode 100644 index 000000000..94376f001 --- /dev/null +++ b/packages/kadira-flow-router/test/client/_helpers.js @@ -0,0 +1,10 @@ +GetSub = function (name) { + for(var id in Meteor.connection._subscriptions) { + var sub = Meteor.connection._subscriptions[id]; + if(name === sub.name) { + return sub; + } + } +}; + +FlowRouter.route('/'); diff --git a/packages/kadira-flow-router/test/client/group.spec.js b/packages/kadira-flow-router/test/client/group.spec.js new file mode 100644 index 000000000..06e793ba9 --- /dev/null +++ b/packages/kadira-flow-router/test/client/group.spec.js @@ -0,0 +1,113 @@ +Tinytest.add('Client - Group - validate path definition', function (test, next) { + // path & prefix must start with '/' + test.throws(function() { + new Group(null, {prefix: Random.id()}); + }); + + var group = FlowRouter.group({prefix: '/' + Random.id()}); + + test.throws(function() { + group.route(Random.id()); + }); +}); + +Tinytest.addAsync('Client - Group - define and go to route with prefix', function (test, next) { + var prefix = Random.id(); + var rand = Random.id(); + var rendered = 0; + + var group = FlowRouter.group({prefix: '/' + prefix}); + + group.route('/' + rand, { + action: function(_params) { + rendered++; + } + }); + + FlowRouter.go('/' + prefix + '/' + rand); + + setTimeout(function() { + test.equal(rendered, 1); + setTimeout(next, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Group - define and go to route without prefix', function (test, next) { + var rand = Random.id(); + var rendered = 0; + + var group = FlowRouter.group(); + + group.route('/' + rand, { + action: function(_params) { + rendered++; + } + }); + + FlowRouter.go('/' + rand); + + setTimeout(function() { + test.equal(rendered, 1); + setTimeout(next, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Group - subscribe', function (test, next) { + var rand = Random.id(); + + var group = FlowRouter.group({ + subscriptions: function (params) { + this.register('baz', Meteor.subscribe('baz')); + } + }); + + group.route('/' + rand); + + FlowRouter.go('/' + rand); + setTimeout(function() { + test.isTrue(!!GetSub('baz')); + next(); + }, 100); +}); + + +Tinytest.addAsync('Client - Group - set and retrieve group name', function (test, next) { + var rand = Random.id(); + var name = Random.id(); + + var group = FlowRouter.group({ + name: name + }); + + group.route('/' + rand); + + FlowRouter.go('/' + rand); + setTimeout(function() { + test.isTrue(FlowRouter.current().route.group.name === name); + next(); + }, 100); +}); + +Tinytest.add('Client - Group - expose group options on a route', function (test) { + var pathDef = "/" + Random.id(); + var name = Random.id(); + var groupName = Random.id(); + var data = {aa: 10}; + var layout = 'blah'; + + var group = FlowRouter.group({ + name: groupName, + prefix: '/admin', + layout: layout, + someData: data + }); + + group.route(pathDef, { + name: name + }); + + var route = FlowRouter._routesMap[name]; + + test.equal(route.group.options.someData, data); + test.equal(route.group.options.layout, layout); +}); diff --git a/packages/kadira-flow-router/test/client/loader.spec.js b/packages/kadira-flow-router/test/client/loader.spec.js new file mode 100644 index 000000000..091c2e021 --- /dev/null +++ b/packages/kadira-flow-router/test/client/loader.spec.js @@ -0,0 +1,17 @@ +Router = FlowRouter.Router; + + +Tinytest.add('Client - import page.js', function (test) { + test.isTrue(!!Router.prototype._page); + test.isFalse(!!window.page); +}); + + +Tinytest.add('Client - import query.js', function (test) { + test.isTrue(!!Router.prototype._qs); +}); + + +Tinytest.add('Client - create FlowRouter', function (test) { + test.isTrue(!!FlowRouter); +}); diff --git a/packages/kadira-flow-router/test/client/route.reactivity.spec.js b/packages/kadira-flow-router/test/client/route.reactivity.spec.js new file mode 100644 index 000000000..c6c441839 --- /dev/null +++ b/packages/kadira-flow-router/test/client/route.reactivity.spec.js @@ -0,0 +1,158 @@ +Route = FlowRouter.Route; + + +Tinytest.addAsync('Client - Route - Reactivity - getParam', function (test, done) { + var r = new Route(); + Tracker.autorun(function(c) { + var param = r.getParam("id"); + if(param) { + test.equal(param, "hello"); + c.stop(); + Meteor.defer(done); + } + }); + + setTimeout(function() { + var context = { + params: {id: "hello"}, + queryParams: {} + }; + r.registerRouteChange(context); + }, 10); +}); + +Tinytest.addAsync('Client - Route - Reactivity - getParam on route close', function (test, done) { + var r = new Route(); + var closeTriggered = false; + Tracker.autorun(function(c) { + var param = r.getParam("id"); + if(closeTriggered) { + test.equal(param, undefined); + c.stop(); + Meteor.defer(done); + } + }); + + setTimeout(function() { + closeTriggered = true; + r.registerRouteClose(); + }, 10); +}); + +Tinytest.addAsync('Client - Route - Reactivity - getQueryParam', function (test, done) { + var r = new Route(); + Tracker.autorun(function(c) { + var param = r.getQueryParam("id"); + if(param) { + test.equal(param, "hello"); + c.stop(); + Meteor.defer(done); + } + }); + + setTimeout(function() { + var context = { + params: {}, + queryParams: {id: "hello"} + }; + r.registerRouteChange(context); + }, 10); +}); + +Tinytest.addAsync('Client - Route - Reactivity - getQueryParam on route close', function (test, done) { + var r = new Route(); + var closeTriggered = false; + Tracker.autorun(function(c) { + var param = r.getQueryParam("id"); + if(closeTriggered) { + test.equal(param, undefined); + c.stop(); + Meteor.defer(done); + } + }); + + setTimeout(function() { + closeTriggered = true; + r.registerRouteClose(); + }, 10); +}); + +Tinytest.addAsync('Client - Route - Reactivity - getRouteName rerun when route closed', function (test, done) { + var r = new Route(); + r.name = "my-route"; + var closeTriggered = false; + + Tracker.autorun(function(c) { + var name = r.getRouteName(); + test.equal(name, r.name); + + if(closeTriggered) { + c.stop(); + Meteor.defer(done); + } + }); + + setTimeout(function() { + closeTriggered = true; + r.registerRouteClose(); + }, 10); +}); + +Tinytest.addAsync('Client - Route - Reactivity - watchPathChange when routeChange', function (test, done) { + var r = new Route(); + var pathChangeCounts = 0; + + var c = Tracker.autorun(function() { + r.watchPathChange(); + pathChangeCounts++; + }); + + var context = { + params: {}, + queryParams: {} + }; + + setTimeout(function() { + r.registerRouteChange(context); + setTimeout(checkAfterNormalRouteChange, 50); + }, 10); + + function checkAfterNormalRouteChange() { + test.equal(pathChangeCounts, 2); + var lastRouteChange = true; + r.registerRouteChange(context, lastRouteChange); + setTimeout(checkAfterLastRouteChange, 10); + } + + function checkAfterLastRouteChange() { + test.equal(pathChangeCounts, 2); + c.stop(); + Meteor.defer(done); + } +}); + +Tinytest.addAsync('Client - Route - Reactivity - watchPathChange when routeClose', function (test, done) { + var r = new Route(); + var pathChangeCounts = 0; + + var c = Tracker.autorun(function() { + r.watchPathChange(); + pathChangeCounts++; + }); + + var context = { + params: {}, + queryParams: {} + }; + + setTimeout(function() { + r.registerRouteClose(); + setTimeout(checkAfterRouteClose, 10); + }, 10); + + function checkAfterRouteClose() { + test.equal(pathChangeCounts, 2); + c.stop(); + Meteor.defer(done); + } +}); \ No newline at end of file diff --git a/packages/kadira-flow-router/test/client/router.core.spec.js b/packages/kadira-flow-router/test/client/router.core.spec.js new file mode 100644 index 000000000..160c9112e --- /dev/null +++ b/packages/kadira-flow-router/test/client/router.core.spec.js @@ -0,0 +1,632 @@ +Router = FlowRouter.Router; + +Tinytest.addAsync('Client - Router - define and go to route', function (test, next) { + var rand = Random.id(); + var rendered = 0; + + FlowRouter.route('/' + rand, { + action: function(_params) { + rendered++; + } + }); + + FlowRouter.go('/' + rand); + + setTimeout(function() { + test.equal(rendered, 1); + setTimeout(next, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Router - define and go to route with fields', +function (test, next) { + var rand = Random.id(); + var pathDef = "/" + rand + "/:key"; + var rendered = 0; + + FlowRouter.route(pathDef, { + action: function(params) { + test.equal(params.key, "abc +@%"); + rendered++; + } + }); + + FlowRouter.go(pathDef, {key: "abc +@%"}); + + setTimeout(function() { + test.equal(rendered, 1); + setTimeout(next, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Router - parse params and query', function (test, next) { + var rand = Random.id(); + var rendered = 0; + var params = null; + + FlowRouter.route('/' + rand + '/:foo', { + action: function(_params) { + rendered++; + params = _params; + } + }); + + FlowRouter.go('/' + rand + '/bar'); + + setTimeout(function() { + test.equal(rendered, 1); + test.equal(params.foo, 'bar'); + setTimeout(next, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Router - redirect using FlowRouter.go', function (test, next) { + var rand = Random.id(), rand2 = Random.id(); + var log = []; + var paths = ['/' + rand2, '/' + rand]; + var done = false; + + FlowRouter.route(paths[0], { + action: function(_params) { + log.push(1); + FlowRouter.go(paths[1]); + } + }); + + FlowRouter.route(paths[1], { + action: function(_params) { + log.push(2); + } + }); + + FlowRouter.go(paths[0]); + + setTimeout(function() { + test.equal(log, [1, 2]); + done = true; + next(); + }, 100); +}); + +Tinytest.addAsync('Client - Router - get current route path', function (test, next) { + var value = Random.id(); + var randomValue = Random.id(); + var pathDef = "/" + randomValue + '/:_id'; + var path = "/" + randomValue + "/" + value; + + var detectedValue = null; + + FlowRouter.route(pathDef, { + action: function(params) { + detectedValue = params._id; + } + }); + + FlowRouter.go(path); + + Meteor.setTimeout(function() { + test.equal(detectedValue, value); + test.equal(FlowRouter.current().path, path); + next(); + }, 50); +}); + +Tinytest.addAsync('Client - Router - subscribe to global subs', function (test, next) { + var rand = Random.id(); + FlowRouter.route('/' + rand); + + FlowRouter.subscriptions = function (path) { + test.equal(path, '/' + rand); + this.register('baz', Meteor.subscribe('baz')); + }; + + FlowRouter.go('/' + rand); + setTimeout(function() { + test.isTrue(!!GetSub('baz')); + FlowRouter.subscriptions = Function.prototype; + next(); + }, 100); +}); + +Tinytest.addAsync('Client - Router - setParams - generic', function (test, done) { + var randomKey = Random.id(); + var pathDef = "/" + randomKey + "/:cat/:id"; + var paramsList = []; + FlowRouter.route(pathDef, { + action: function(params) { + paramsList.push(params); + } + }); + + FlowRouter.go(pathDef, {cat: "meteor", id: "200"}); + setTimeout(function() { + // return done(); + var success = FlowRouter.setParams({id: "700"}); + test.isTrue(success); + setTimeout(validate, 50); + }, 50); + + function validate() { + test.equal(paramsList.length, 2); + test.equal(_.pick(paramsList[0], "id", "cat"), {cat: "meteor", id: "200"}); + test.equal(_.pick(paramsList[1], "id", "cat"), {cat: "meteor", id: "700"}); + done(); + } +}); + +Tinytest.addAsync('Client - Router - setParams - preserve query strings', function (test, done) { + var randomKey = Random.id(); + var pathDef = "/" + randomKey + "/:cat/:id"; + var paramsList = []; + var queryParamsList = []; + + FlowRouter.route(pathDef, { + action: function(params, queryParams) { + paramsList.push(params); + queryParamsList.push(queryParams); + } + }); + + FlowRouter.go(pathDef, {cat: "meteor", id: "200 +% / ad"}, {aa: "20 +%"}); + setTimeout(function() { + // return done(); + var success = FlowRouter.setParams({id: "700 +% / ad"}); + test.isTrue(success); + setTimeout(validate, 50); + }, 50); + + function validate() { + test.equal(paramsList.length, 2); + test.equal(queryParamsList.length, 2); + + test.equal(_.pick(paramsList[0], "id", "cat"), {cat: "meteor", id: "200 +% / ad"}); + test.equal(_.pick(paramsList[1], "id", "cat"), {cat: "meteor", id: "700 +% / ad"}); + test.equal(queryParamsList, [{aa: "20 +%"}, {aa: "20 +%"}]); + done(); + } +}); + +Tinytest.add('Client - Router - setParams - no route selected', function (test) { + var originalRoute = FlowRouter._current.route; + FlowRouter._current.route = undefined; + var success = FlowRouter.setParams({id: "800"}); + test.isFalse(success); + FlowRouter._current.route = originalRoute; +}); + +Tinytest.addAsync('Client - Router - setQueryParams - using check', function (test, done) { + var randomKey = Random.id(); + var pathDef = "/" + randomKey + ""; + var queryParamsList = []; + FlowRouter.route(pathDef, { + action: function(params, queryParams) { + queryParamsList.push(queryParams); + } + }); + + FlowRouter.go(pathDef, {}, {cat: "meteor", id: "200"}); + setTimeout(function() { + check(FlowRouter.current().queryParams, {cat: String, id: String}); + done(); + }, 50); +}); + +Tinytest.addAsync('Client - Router - setQueryParams - generic', function (test, done) { + var randomKey = Random.id(); + var pathDef = "/" + randomKey + ""; + var queryParamsList = []; + FlowRouter.route(pathDef, { + action: function(params, queryParams) { + queryParamsList.push(queryParams); + } + }); + + FlowRouter.go(pathDef, {}, {cat: "meteor", id: "200"}); + setTimeout(function() { + // return done(); + var success = FlowRouter.setQueryParams({id: "700"}); + test.isTrue(success); + setTimeout(validate, 50); + }, 50); + + function validate() { + test.equal(queryParamsList.length, 2); + test.equal(_.pick(queryParamsList[0], "id", "cat"), {cat: "meteor", id: "200"}); + test.equal(_.pick(queryParamsList[1], "id", "cat"), {cat: "meteor", id: "700"}); + done(); + } +}); + +Tinytest.addAsync('Client - Router - setQueryParams - remove query param null', function (test, done) { + var randomKey = Random.id(); + var pathDef = "/" + randomKey + ""; + var queryParamsList = []; + FlowRouter.route(pathDef, { + action: function(params, queryParams) { + queryParamsList.push(queryParams); + } + }); + + FlowRouter.go(pathDef, {}, {cat: "meteor", id: "200"}); + setTimeout(function() { + var success = FlowRouter.setQueryParams({id: "700", cat: null}); + test.isTrue(success); + setTimeout(validate, 50); + }, 50); + + function validate() { + test.equal(queryParamsList.length, 2); + test.equal(_.pick(queryParamsList[0], "id", "cat"), {cat: "meteor", id: "200"}); + test.equal(queryParamsList[1], {id: "700"}); + done(); + } +}); + +Tinytest.addAsync('Client - Router - setQueryParams - remove query param undefined', function (test, done) { + var randomKey = Random.id(); + var pathDef = "/" + randomKey + ""; + var queryParamsList = []; + FlowRouter.route(pathDef, { + action: function(params, queryParams) { + queryParamsList.push(queryParams); + } + }); + + FlowRouter.go(pathDef, {}, {cat: "meteor", id: "200"}); + setTimeout(function() { + var success = FlowRouter.setQueryParams({id: "700", cat: undefined}); + test.isTrue(success); + setTimeout(validate, 50); + }, 50); + + function validate() { + test.equal(queryParamsList.length, 2); + test.equal(_.pick(queryParamsList[0], "id", "cat"), {cat: "meteor", id: "200"}); + test.equal(queryParamsList[1], {id: "700"}); + done(); + } +}); + +Tinytest.addAsync('Client - Router - setQueryParams - preserve params', function (test, done) { + var randomKey = Random.id(); + var pathDef = "/" + randomKey + "/:abc"; + var queryParamsList = []; + var paramsList = []; + FlowRouter.route(pathDef, { + action: function(params, queryParams) { + paramsList.push(params); + queryParamsList.push(queryParams); + } + }); + + FlowRouter.go(pathDef, {abc: "20"}, {cat: "meteor", id: "200"}); + setTimeout(function() { + // return done(); + var success = FlowRouter.setQueryParams({id: "700"}); + test.isTrue(success); + setTimeout(validate, 50); + }, 50); + + function validate() { + test.equal(queryParamsList.length, 2); + test.equal(queryParamsList, [ + {cat: "meteor", id: "200"}, {cat: "meteor", id: "700"} + ]); + + test.equal(paramsList.length, 2); + test.equal(_.pick(paramsList[0], "abc"), {abc: "20"}); + test.equal(_.pick(paramsList[1], "abc"), {abc: "20"}); + done(); + } +}); + +Tinytest.add('Client - Router - setQueryParams - no route selected', function (test) { + var originalRoute = FlowRouter._current.route; + FlowRouter._current.route = undefined; + var success = FlowRouter.setQueryParams({id: "800"}); + test.isFalse(success); + FlowRouter._current.route = originalRoute; +}); + +Tinytest.addAsync('Client - Router - notFound', function (test, done) { + var data = []; + FlowRouter.notFound = { + subscriptions: function() { + data.push("subscriptions"); + }, + action: function() { + data.push("action"); + } + }; + + FlowRouter.go("/" + Random.id()); + setTimeout(function() { + test.equal(data, ["subscriptions", "action"]); + done(); + }, 50); +}); + +Tinytest.addAsync('Client - Router - withReplaceState - enabled', +function (test, done) { + var pathDef = "/" + Random.id() + "/:id"; + var originalRedirect = FlowRouter._page.replace; + var callCount = 0; + FlowRouter._page.replace = function(path) { + callCount++; + originalRedirect.call(FlowRouter._page, path); + }; + + FlowRouter.route(pathDef, { + name: name, + action: function(params) { + test.equal(params.id, "awesome"); + test.equal(callCount, 1); + FlowRouter._page.replace = originalRedirect; + // We don't use Meteor.defer here since it carries + // Meteor.Environment vars too + // Which breaks our test below + setTimeout(done, 0); + } + }); + + FlowRouter.withReplaceState(function() { + FlowRouter.go(pathDef, {id: "awesome"}); + }); +}); + +Tinytest.addAsync('Client - Router - withReplaceState - disabled', +function (test, done) { + var pathDef = "/" + Random.id() + "/:id"; + var originalRedirect = FlowRouter._page.replace; + var callCount = 0; + FlowRouter._page.replace = function(path) { + callCount++; + originalRedirect.call(FlowRouter._page, path); + }; + + FlowRouter.route(pathDef, { + name: name, + action: function(params) { + test.equal(params.id, "awesome"); + test.equal(callCount, 0); + FlowRouter._page.replace = originalRedirect; + Meteor.defer(done); + } + }); + + FlowRouter.go(pathDef, {id: "awesome"}); +}); + +Tinytest.addAsync('Client - Router - withTrailingSlash - enabled', function (test, next) { + var rand = Random.id(); + var rendered = 0; + + FlowRouter.route('/' + rand, { + action: function(_params) { + rendered++; + } + }); + + FlowRouter.withTrailingSlash(function() { + FlowRouter.go('/' + rand); + }); + + setTimeout(function() { + test.equal(rendered, 1); + test.equal(_.last(location.href), '/'); + setTimeout(next, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Router - idempotent routing - action', +function (test, done) { + var rand = Random.id(); + var pathDef = "/" + rand; + var rendered = 0; + + FlowRouter.route(pathDef, { + action: function(params) { + rendered++; + } + }); + + FlowRouter.go(pathDef); + + Meteor.defer(function() { + FlowRouter.go(pathDef); + + Meteor.defer(function() { + test.equal(rendered, 1); + done(); + }); + }); +}); + +Tinytest.addAsync('Client - Router - idempotent routing - triggers', +function (test, next) { + var rand = Random.id(); + var pathDef = "/" + rand; + var runnedTriggers = 0; + var done = false; + + var triggerFns = [function(params) { + if (done) return; + + runnedTriggers++; + }]; + + FlowRouter.triggers.enter(triggerFns); + + FlowRouter.route(pathDef, { + triggersEnter: triggerFns, + triggersExit: triggerFns + }); + + FlowRouter.go(pathDef); + + FlowRouter.triggers.exit(triggerFns); + + Meteor.defer(function() { + FlowRouter.go(pathDef); + + Meteor.defer(function() { + test.equal(runnedTriggers, 2); + done = true; + next(); + }); + }); +}); + +Tinytest.addAsync('Client - Router - reload - action', +function (test, done) { + var rand = Random.id(); + var pathDef = "/" + rand; + var rendered = 0; + + FlowRouter.route(pathDef, { + action: function(params) { + rendered++; + } + }); + + FlowRouter.go(pathDef); + + Meteor.defer(function() { + FlowRouter.reload(); + + Meteor.defer(function() { + test.equal(rendered, 2); + done(); + }); + }); +}); + +Tinytest.addAsync('Client - Router - reload - triggers', +function (test, next) { + var rand = Random.id(); + var pathDef = "/" + rand; + var runnedTriggers = 0; + var done = false; + + var triggerFns = [function(params) { + if (done) return; + + runnedTriggers++; + }]; + + FlowRouter.triggers.enter(triggerFns); + + FlowRouter.route(pathDef, { + triggersEnter: triggerFns, + triggersExit: triggerFns + }); + + FlowRouter.go(pathDef); + + FlowRouter.triggers.exit(triggerFns); + + Meteor.defer(function() { + FlowRouter.reload(); + + Meteor.defer(function() { + test.equal(runnedTriggers, 6); + done = true; + next(); + }); + }); +}); + +Tinytest.addAsync( +'Client - Router - wait - before initialize', +function(test, done) { + FlowRouter._initialized = false; + FlowRouter.wait(); + test.equal(FlowRouter._askedToWait, true); + + FlowRouter._initialized = true; + FlowRouter._askedToWait = false; + done(); +}); + +Tinytest.addAsync( +'Client - Router - wait - after initialized', +function(test, done) { + try { + FlowRouter.wait(); + } catch(ex) { + test.isTrue(/can't wait/.test(ex.message)); + done(); + } +}); + +Tinytest.addAsync( +'Client - Router - initialize - after initialized', +function(test, done) { + try { + FlowRouter.initialize(); + } catch(ex) { + test.isTrue(/already initialized/.test(ex.message)); + done(); + } +}); + +Tinytest.addAsync( +'Client - Router - base path - url updated', +function(test, done) { + var simulatedBasePath = '/flow'; + var rand = Random.id(); + FlowRouter.route('/' + rand, { action: function() {} }); + + setBasePath(simulatedBasePath); + FlowRouter.go('/' + rand); + setTimeout(function() { + test.equal(location.pathname, simulatedBasePath + '/' + rand); + resetBasePath(); + done(); + }, 100); +}); + +Tinytest.addAsync( +'Client - Router - base path - route action called', +function(test, done) { + var simulatedBasePath = '/flow'; + var rand = Random.id(); + FlowRouter.route('/' + rand, { + action: function() { + resetBasePath(); + done(); + } + }); + + setBasePath(simulatedBasePath); + FlowRouter.go('/' + rand); +}); + +Tinytest.add( +'Client - Router - base path - path generation', +function(test, done) { + _.each(['/flow', '/flow/', 'flow/', 'flow'], function(simulatedBasePath) { + var rand = Random.id(); + setBasePath(simulatedBasePath); + test.equal(FlowRouter.path('/' + rand), '/flow/' + rand); + }); + resetBasePath(); +}); + + +function setBasePath(path) { + FlowRouter._initialized = false; + FlowRouter._basePath = path; + FlowRouter.initialize(); +} + +var defaultBasePath = FlowRouter._basePath; +function resetBasePath() { + setBasePath(defaultBasePath); +} + +function bind(obj, method) { + return function() { + obj[method].apply(obj, arguments); + }; +} diff --git a/packages/kadira-flow-router/test/client/router.reactivity.spec.js b/packages/kadira-flow-router/test/client/router.reactivity.spec.js new file mode 100644 index 000000000..b06deedae --- /dev/null +++ b/packages/kadira-flow-router/test/client/router.reactivity.spec.js @@ -0,0 +1,208 @@ +Tinytest.addAsync( +'Client - Router - Reactivity - detectChange only once', +function (test, done) { + var route = "/" + Random.id(); + var name = Random.id(); + FlowRouter.route(route, {name: name}); + + var ranCount = 0; + var pickedId = null; + var c = Tracker.autorun(function() { + ranCount++; + pickedId = FlowRouter.getQueryParam("id"); + if(pickedId) { + test.equal(pickedId, "hello"); + test.equal(ranCount, 2); + c.stop(); + Meteor.defer(done); + } + }); + + setTimeout(function() { + FlowRouter.go(name, {}, {id: "hello"}); + }, 2); +}); + +Tinytest.addAsync( +'Client - Router - Reactivity - detectChange in the action', +function (test, done) { + var route = "/" + Random.id(); + var name = Random.id(); + FlowRouter.route(route, { + name: name, + action: function() { + var id = FlowRouter.getQueryParam("id"); + test.equal(id, "hello"); + Meteor.defer(done); + } + }); + + setTimeout(function() { + FlowRouter.go(name, {}, {id: "hello"}); + }, 2); +}); + +Tinytest.addAsync( +'Client - Router - Reactivity - detect prev routeChange after new action', +function (test, done) { + var route1 = "/" + Random.id(); + var name1 = Random.id(); + var pickedName1 = null; + + var route2 = "/" + Random.id(); + var name2 = Random.id(); + var pickedName2 = Random.id(); + + FlowRouter.route(route1, { + name: name1, + action: function() { + Tracker.autorun(function(c) { + pickedName1 = FlowRouter.getRouteName(); + if(pickedName1 == name2) { + test.equal(pickedName1, pickedName2); + c.stop(); + Meteor.defer(done); + } + }); + } + }); + + FlowRouter.route(route2, { + name: name2, + action: function() { + pickedName2 = FlowRouter.getRouteName(); + test.equal(pickedName1, name1); + test.equal(pickedName2, name2); + } + }); + + FlowRouter.go(name1); + Meteor.setTimeout(function() { + FlowRouter.go(name2); + }, 10); +}); + +Tinytest.addAsync( +'Client - Router - Reactivity - defer watchPathChange until new route rendered', +function(test, done) { + var route1 = "/" + Random.id(); + var name1 = Random.id(); + var pickedName1 = null; + + var route2 = "/" + Random.id(); + var name2 = Random.id(); + var pickedName2 = Random.id(); + + FlowRouter.route(route1, { + name: name1, + action: function() { + Tracker.autorun(function(c) { + FlowRouter.watchPathChange(); + pickedName1 = FlowRouter.current().route.name; + if(pickedName1 == name2) { + test.equal(pickedName1, pickedName2); + c.stop(); + Meteor.defer(done); + } + }); + } + }); + + FlowRouter.route(route2, { + name: name2, + action: function() { + pickedName2 = FlowRouter.current().route.name; + test.equal(pickedName1, name1); + test.equal(pickedName2, name2); + } + }); + + FlowRouter.go(name1); + Meteor.setTimeout(function() { + FlowRouter.go(name2); + }, 10); +}); + +Tinytest.addAsync( +'Client - Router - Reactivity - reactive changes and trigger redirects', +function(test, done) { + var name1 = Random.id(); + var route1 = "/" + name1; + FlowRouter.route(route1, { + name: name1 + }); + + var name2 = Random.id(); + var route2 = "/" + name2; + FlowRouter.route(route2, { + name: name2, + triggersEnter: [function(context, redirect) { + redirect(name3); + }] + }); + + + var name3 = Random.id(); + var route3 = "/" + name3; + FlowRouter.route(route3, { + name: name3 + }); + + var routeNamesFired = []; + FlowRouter.go(name1); + + var c = null; + setTimeout(function() { + c = Tracker.autorun(function(c) { + routeNamesFired.push(FlowRouter.getRouteName()); + }); + FlowRouter.go(name2); + }, 50); + + setTimeout(function() { + c.stop(); + test.equal(routeNamesFired, [name1, name3]); + Meteor.defer(done); + }, 250); +}); + +Tinytest.addAsync( +'Client - Router - Reactivity - watchPathChange for every route change', +function(test, done) { + var route1 = "/" + Random.id(); + var name1 = Random.id(); + var pickedName1 = null; + + var route2 = "/" + Random.id(); + var name2 = Random.id(); + var pickedName2 = Random.id(); + + FlowRouter.route(route1, { + name: name1 + }); + + FlowRouter.route(route2, { + name: name2 + }); + + var ids = []; + var c = Tracker.autorun(function() { + FlowRouter.watchPathChange(); + ids.push(FlowRouter.current().queryParams['id']); + }); + + FlowRouter.go(name1, {}, {id: "one"}); + Meteor.setTimeout(function() { + FlowRouter.go(name1, {}, {id: "two"}); + }, 10); + + Meteor.setTimeout(function() { + FlowRouter.go(name2, {}, {id: "three"}); + }, 20); + + Meteor.setTimeout(function() { + test.equal(ids, [undefined, "one", "two", "three"]); + c.stop(); + done(); + }, 40); +}); \ No newline at end of file diff --git a/packages/kadira-flow-router/test/client/router.subs_ready.spec.js b/packages/kadira-flow-router/test/client/router.subs_ready.spec.js new file mode 100644 index 000000000..8a20077a1 --- /dev/null +++ b/packages/kadira-flow-router/test/client/router.subs_ready.spec.js @@ -0,0 +1,225 @@ +Tinytest.addAsync('Client - Router - subsReady - with no args - all subscriptions ready', function (test, next) { + var rand = Random.id(); + FlowRouter.route('/' + rand, { + subscriptions: function(params) { + this.register('bar', Meteor.subscribe('bar')); + this.register('foo', Meteor.subscribe('foo')); + } + }); + + FlowRouter.subscriptions = function () { + this.register('baz', Meteor.subscribe('baz')); + }; + + FlowRouter.go('/' + rand); + + Tracker.autorun(function(c) { + if(FlowRouter.subsReady()) { + FlowRouter.subscriptions = Function.prototype; + next(); + c.stop(); + } + }); +}); + +Tinytest.addAsync('Client - Router - subsReady - with no args - all subscriptions does not ready', function (test, next) { + var rand = Random.id(); + FlowRouter.route('/' + rand, { + subscriptions: function(params) { + this.register('fooNotReady', Meteor.subscribe('fooNotReady')); + } + }); + + FlowRouter.subscriptions = function () { + this.register('bazNotReady', Meteor.subscribe('bazNotReady')); + }; + + FlowRouter.go('/' + rand); + setTimeout(function() { + test.isTrue(!FlowRouter.subsReady()); + FlowRouter.subscriptions = Function.prototype; + next(); + }, 100); +}); + +Tinytest.addAsync('Client - Router - subsReady - with no args - global subscriptions does not ready', function (test, next) { + var rand = Random.id(); + FlowRouter.route('/' + rand, { + subscriptions: function(params) { + this.register('bar', Meteor.subscribe('bar')); + this.register('foo', Meteor.subscribe('foo')); + } + }); + + FlowRouter.subscriptions = function () { + this.register('bazNotReady', Meteor.subscribe('bazNotReady')); + }; + + FlowRouter.go('/' + rand); + setTimeout(function() { + test.isTrue(!FlowRouter.subsReady()); + FlowRouter.subscriptions = Function.prototype; + next(); + }, 100); +}); + +Tinytest.addAsync('Client - Router - subsReady - with no args - current subscriptions does not ready', function (test, next) { + var rand = Random.id(); + FlowRouter.route('/' + rand, { + subscriptions: function(params) { + this.register('bar', Meteor.subscribe('bar')); + this.register('fooNotReady', Meteor.subscribe('fooNotReady')); + } + }); + + FlowRouter.subscriptions = function () { + this.register('baz', Meteor.subscribe('baz')); + }; + + FlowRouter.go('/' + rand); + setTimeout(function() { + test.isTrue(!FlowRouter.subsReady()); + FlowRouter.subscriptions = Function.prototype; + next(); + }, 100); +}); + +Tinytest.addAsync('Client - Router - subsReady - with args - all subscriptions ready', function (test, next) { + var rand = Random.id(); + FlowRouter.route('/' + rand, { + subscriptions: function(params) { + this.register('bar', Meteor.subscribe('bar')); + this.register('foo', Meteor.subscribe('foo')); + } + }); + + FlowRouter.subscriptions = function () { + this.register('baz', Meteor.subscribe('baz')); + }; + + FlowRouter.go('/' + rand); + Tracker.autorun(function(c) { + if(FlowRouter.subsReady('foo', 'baz')) { + FlowRouter.subscriptions = Function.prototype; + next(); + c.stop(); + } + }); +}); + +Tinytest.addAsync('Client - Router - subsReady - with args - all subscriptions does not ready', function (test, next) { + var rand = Random.id(); + FlowRouter.route('/' + rand, { + subscriptions: function(params) { + this.register('fooNotReady', Meteor.subscribe('fooNotReady')); + } + }); + + FlowRouter.subscriptions = function () { + this.register('bazNotReady', Meteor.subscribe('bazNotReady')); + }; + + FlowRouter.go('/' + rand); + setTimeout(function() { + test.isTrue(!FlowRouter.subsReady('fooNotReady', 'bazNotReady')); + FlowRouter.subscriptions = Function.prototype; + next(); + }, 100); +}); + +Tinytest.addAsync('Client - Router - subsReady - with args - global subscriptions does not ready', function (test, next) { + var rand = Random.id(); + FlowRouter.route('/' + rand, { + subscriptions: function(params) { + this.register('bar', Meteor.subscribe('bar')); + this.register('foo', Meteor.subscribe('foo')); + } + }); + + FlowRouter.subscriptions = function () { + this.register('bazNotReady', Meteor.subscribe('bazNotReady')); + }; + + FlowRouter.go('/' + rand); + setTimeout(function() { + test.isTrue(!FlowRouter.subsReady('foo', 'bazNotReady')); + FlowRouter.subscriptions = Function.prototype; + next(); + }, 100); +}); + +Tinytest.addAsync('Client - Router - subsReady - with args - current subscriptions does not ready', function (test, next) { + var rand = Random.id(); + FlowRouter.route('/' + rand, { + subscriptions: function(params) { + this.register('bar', Meteor.subscribe('bar')); + this.register('fooNotReady', Meteor.subscribe('fooNotReady')); + } + }); + + FlowRouter.subscriptions = function () { + this.register('baz', Meteor.subscribe('baz')); + }; + + FlowRouter.go('/' + rand); + setTimeout(function() { + test.isTrue(!FlowRouter.subsReady('fooNotReady', 'baz')); + FlowRouter.subscriptions = Function.prototype; + next(); + }, 100); +}); + +Tinytest.addAsync('Client - Router - subsReady - with args - subscribe with wrong name', function (test, next) { + var rand = Random.id(); + FlowRouter.route('/' + rand, { + subscriptions: function(params) { + this.register('bar', Meteor.subscribe('bar')); + } + }); + + FlowRouter.subscriptions = function () { + this.register('baz', Meteor.subscribe('baz')); + }; + + FlowRouter.go('/' + rand); + setTimeout(function() { + test.isTrue(!FlowRouter.subsReady('baz', 'xxx', 'baz')); + FlowRouter.subscriptions = Function.prototype; + next(); + }, 100); +}); + +Tinytest.addAsync('Client - Router - subsReady - with args - same route two different subs', function (test, next) { + var rand = Random.id(); + var count = 0; + FlowRouter.route('/' + rand, { + subscriptions: function(params) { + if(++count == 1) { + this.register('not-exisitng', Meteor.subscribe('not-exisitng')); + } + } + }); + + FlowRouter.subscriptions = Function.prototype; + FlowRouter.go('/' + rand); + setTimeout(function() { + test.isFalse(FlowRouter.subsReady()); + FlowRouter.go('/' + rand, {}, {param: "111"}); + setTimeout(function() { + test.isTrue(FlowRouter.subsReady()); + next(); + }, 100) + }, 100); +}); + +Tinytest.addAsync('Client - Router - subsReady - no subscriptions - simple', function (test, next) { + var rand = Random.id(); + FlowRouter.route('/' + rand, {}); + FlowRouter.subscriptions = Function.prototype; + + FlowRouter.go('/' + rand); + setTimeout(function() { + test.isTrue(FlowRouter.subsReady()); + next(); + }, 100); +}); \ No newline at end of file diff --git a/packages/kadira-flow-router/test/client/trigger.spec.js b/packages/kadira-flow-router/test/client/trigger.spec.js new file mode 100644 index 000000000..319c6bd28 --- /dev/null +++ b/packages/kadira-flow-router/test/client/trigger.spec.js @@ -0,0 +1,570 @@ +Tinytest.addAsync('Client - Triggers - global enter triggers', function(test, next) { + var rand = Random.id(), rand2 = Random.id(); + var log = []; + var paths = ['/' + rand2, '/' + rand]; + var done = false; + + FlowRouter.route('/' + rand, { + action: function(_params) { + log.push(1); + } + }); + + FlowRouter.route('/' + rand2, { + action: function(_params) { + log.push(2); + } + }); + + FlowRouter.triggers.enter([function(context) { + if(done) return; + test.equal(context.path, paths.pop()); + log.push(0); + }]); + + FlowRouter.go('/' + rand); + + setTimeout(function() { + FlowRouter.go('/' + rand2); + + setTimeout(function() { + test.equal(log, [0, 1, 0, 2]); + done = true; + setTimeout(next, 100); + }, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Triggers - global enter triggers with "only"', function (test, next) { + var rand = Random.id(), rand2 = Random.id(); + var log = []; + var done = false; + + FlowRouter.route('/' + rand, { + action: function(_params) { + log.push(1); + } + }); + + FlowRouter.route('/' + rand2, { + name: 'foo', + action: function(_params) { + log.push(2); + } + }); + + FlowRouter.triggers.enter([function(context) { + if(done) return; + test.equal(context.path, '/' + rand2); + log.push(8); + }], {only: ['foo']}); + + FlowRouter.go('/' + rand); + + setTimeout(function() { + FlowRouter.go('/' + rand2); + + setTimeout(function() { + test.equal(log, [1, 8, 2]); + done = true; + setTimeout(next, 100); + }, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Triggers - global enter triggers with "except"', function (test, next) { + var rand = Random.id(), rand2 = Random.id(); + var log = []; + var done = false; + + FlowRouter.route('/' + rand, { + action: function(_params) { + log.push(1); + } + }); + + FlowRouter.route('/' + rand2, { + name: 'foo', + action: function(_params) { + log.push(2); + } + }); + + FlowRouter.triggers.enter([function(context) { + if(done) return; + test.equal(context.path, '/' + rand); + log.push(8); + }], {except: ['foo']}); + + FlowRouter.go('/' + rand); + + setTimeout(function() { + FlowRouter.go('/' + rand2); + + setTimeout(function() { + test.equal(log, [8, 1, 2]); + done = true; + setTimeout(next, 100); + }, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Triggers - global exit triggers', function (test, next) { + var rand = Random.id(), rand2 = Random.id(); + var log = []; + var done =false; + + FlowRouter.route('/' + rand, { + action: function(_params) { + log.push(1); + } + }); + + FlowRouter.route('/' + rand2, { + action: function(_params) { + log.push(2); + } + }); + + FlowRouter.go('/' + rand); + + FlowRouter.triggers.exit([function(context) { + if(done) return; + test.equal(context.path, '/' + rand); + log.push(0); + }]); + + setTimeout(function() { + FlowRouter.go('/' + rand2); + + setTimeout(function() { + test.equal(log, [1, 0, 2]); + done = true; + setTimeout(next, 100); + }, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Triggers - global exit triggers with "only"', function (test, next) { + var rand = Random.id(), rand2 = Random.id(); + var log = []; + var done = false; + + FlowRouter.route('/' + rand, { + action: function(_params) { + log.push(1); + } + }); + + FlowRouter.route('/' + rand2, { + name: 'foo', + action: function(_params) { + log.push(2); + } + }); + + FlowRouter.triggers.exit([function(context) { + if(done) return; + test.equal(context.path, '/' + rand2); + log.push(8); + }], {only: ['foo']}); + + FlowRouter.go('/' + rand); + + setTimeout(function() { + FlowRouter.go('/' + rand2); + + setTimeout(function() { + FlowRouter.go('/' + rand); + + setTimeout(function() { + test.equal(log, [1, 2, 8, 1]); + done = true; + setTimeout(next, 100); + }, 100); + }, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Triggers - global exit triggers with "except"', function (test, next) { + var rand = Random.id(), rand2 = Random.id(); + var log = []; + var done = false; + + FlowRouter.route('/' + rand, { + action: function(_params) { + log.push(1); + } + }); + + FlowRouter.route('/' + rand2, { + name: 'foo', + action: function(_params) { + log.push(2); + } + }); + + FlowRouter.go('/' + rand); + + FlowRouter.triggers.exit([function(context) { + if(done) return; + test.equal(context.path, '/' + rand); + log.push(9); + }], {except: ['foo']}); + + + setTimeout(function() { + FlowRouter.go('/' + rand2); + + setTimeout(function() { + FlowRouter.go('/' + rand); + + setTimeout(function() { + test.equal(log, [1, 9, 2, 1]); + done = true; + setTimeout(next, 100); + }, 100); + }, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Triggers - route enter triggers', function (test, next) { + var rand = Random.id(); + var log = []; + + var triggerFn = function (context) { + test.equal(context.path, '/' + rand); + log.push(5); + }; + + FlowRouter.route('/' + rand, { + triggersEnter: [triggerFn], + action: function(_params) { + log.push(1); + } + }); + + FlowRouter.go('/' + rand); + + setTimeout(function() { + test.equal(log, [5, 1]); + setTimeout(next, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Triggers - router exit triggers', function (test, next) { + var rand = Random.id(); + var log = []; + + var triggerFn = function (context) { + test.equal(context.path, '/' + rand); + log.push(6); + }; + + FlowRouter.route('/' + rand, { + triggersExit: [triggerFn], + action: function(_params) { + log.push(1); + } + }); + + FlowRouter.go('/' + rand); + + setTimeout(function() { + FlowRouter.go('/' + Random.id()); + + setTimeout(function() { + test.equal(log, [1, 6]); + setTimeout(next, 100); + }, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Triggers - group enter triggers', function (test, next) { + var rand = Random.id(), rand2 = Random.id(); + var log = []; + var paths = ['/' + rand2, '/' + rand]; + + var triggerFn = function (context) { + test.equal(context.path, paths.pop()); + log.push(3); + }; + + var group = FlowRouter.group({ + triggersEnter: [triggerFn] + }); + + group.route('/' + rand, { + action: function(_params) { + log.push(1); + } + }); + + group.route('/' + rand2, { + action: function(_params) { + log.push(2); + } + }); + + FlowRouter.go('/' + rand); + + setTimeout(function() { + FlowRouter.go('/' + rand2); + + setTimeout(function() { + test.equal(log, [3, 1, 3, 2]); + setTimeout(next, 100); + }, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Triggers - group exit triggers', function (test, next) { + var rand = Random.id(), rand2 = Random.id(); + var log = []; + + var triggerFn = function (context) { + log.push(4); + }; + + var group = FlowRouter.group({ + triggersExit: [triggerFn] + }); + + group.route('/' + rand, { + action: function(_params) { + log.push(1); + } + }); + + group.route('/' + rand2, { + action: function(_params) { + log.push(2); + } + }); + + FlowRouter.go('/' + rand); + + setTimeout(function() { + FlowRouter.go('/' + rand2); + + setTimeout(function() { + test.equal(log, [1, 4, 2]); + setTimeout(next, 100); + }, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Triggers - redirect from enter', function(test, next) { + var rand = Random.id(), rand2 = Random.id(); + var log = []; + + FlowRouter.route('/' + rand, { + triggersEnter: [function(context, redirect) { + redirect("/" + rand2); + }, function() { + throw new Error("should not execute this trigger"); + }], + action: function(_params) { + log.push(1); + }, + name: rand + }); + + FlowRouter.route('/' + rand2, { + action: function(_params) { + log.push(2); + }, + name: rand2 + }); + + FlowRouter.go('/'); + FlowRouter.go('/' + rand); + + setTimeout(function() { + test.equal(log, [2]); + next(); + }, 300); +}); + +Tinytest.addAsync('Client - Triggers - redirect by routeName', function(test, next) { + var rand = Random.id(), rand2 = Random.id(); + var log = []; + + FlowRouter.route('/' + rand, { + name: rand, + triggersEnter: [function(context, redirect) { + redirect(rand2, null, {aa: "bb"}); + }, function() { + throw new Error("should not execute this trigger"); + }], + action: function(_params) { + log.push(1); + }, + name: rand + }); + + FlowRouter.route('/' + rand2, { + name: rand2, + action: function(_params, queryParams) { + log.push(2); + test.equal(queryParams, {aa: "bb"}); + }, + name: rand2 + }); + + FlowRouter.go('/'); + FlowRouter.go('/' + rand); + + setTimeout(function() { + test.equal(log, [2]); + next(); + }, 300); +}); + +Tinytest.addAsync('Client - Triggers - redirect from exit', function(test, next) { + var rand = Random.id(), rand2 = Random.id(), rand3 = Random.id(); + var log = []; + + FlowRouter.route('/' + rand, { + action: function() { + log.push(1); + }, + triggersExit: [ + function(context, redirect) { + redirect('/' + rand3); + }, + function() { + throw new Error("should not call this trigger"); + } + ] + }); + + FlowRouter.route('/' + rand2, { + action: function() { + log.push(2); + } + }); + + FlowRouter.route('/' + rand3, { + action: function() { + log.push(3); + } + }); + + FlowRouter.go('/' + rand); + + setTimeout(function() { + FlowRouter.go('/' + rand2); + + setTimeout(function() { + test.equal(log, [1, 3]); + next(); + }, 100); + }, 100); +}); + +Tinytest.addAsync('Client - Triggers - redirect to external URL fails', function(test, next) { + var rand = Random.id(), rand2 = Random.id(); + var log = []; + + // testing "http://" URLs + FlowRouter.route('/' + rand, { + triggersEnter: [function(context, redirect) { + test.throws(function() { + redirect("http://example.com/") + }, "Redirects to URLs outside of the app are not supported") + }], + action: function(_params) { + log.push(1); + }, + name: rand + }); + + // testing "https://" URLs + FlowRouter.route('/' + rand2, { + triggersEnter: [function(context, redirect) { + test.throws(function() { + redirect("https://example.com/") + }) + }], + action: function(_params) { + log.push(2); + }, + name: rand2 + }); + + FlowRouter.go('/'); + FlowRouter.go('/' + rand); + FlowRouter.go('/' + rand2); + + setTimeout(function() { + test.equal(log, []); + next(); + }, 300); +}); + +Tinytest.addAsync('Client - Triggers - stop callback from enter', function(test, next) { + var rand = Random.id(); + var log = []; + + FlowRouter.route('/' + rand, { + triggersEnter: [function(context, redirect, stop) { + log.push(10); + stop(); + }, function() { + throw new Error("should not execute this trigger"); + }], + action: function(_params) { + throw new Error("should not execute the action"); + } + }); + + FlowRouter.go('/'); + FlowRouter.go('/' + rand); + + setTimeout(function() { + test.equal(log, [10]); + next(); + }, 100); +}); + +Tinytest.addAsync( +'Client - Triggers - invalidate inside an autorun', +function(test, next) { + var rand = Random.id(), rand2 = Random.id(); + var log = []; + var paths = ['/' + rand2, '/' + rand]; + var done = false; + + FlowRouter.route('/' + rand, { + action: function(_params) { + log.push(1); + } + }); + + FlowRouter.route('/' + rand2, { + action: function(_params) { + log.push(2); + } + }); + + FlowRouter.triggers.enter([function(context) { + if(done) return; + test.equal(context.path, paths.pop()); + log.push(0); + }]); + + Tracker.autorun(function(c) { + FlowRouter.go('/' + rand); + }); + + setTimeout(function() { + FlowRouter.go('/' + rand2); + + setTimeout(function() { + test.equal(log, [0, 1, 0, 2]); + done = true; + setTimeout(next, 100); + }, 100); + }, 100); +}); diff --git a/packages/kadira-flow-router/test/client/triggers.js b/packages/kadira-flow-router/test/client/triggers.js new file mode 100644 index 000000000..7eb9a99cf --- /dev/null +++ b/packages/kadira-flow-router/test/client/triggers.js @@ -0,0 +1,297 @@ +Tinytest.addAsync( +'Triggers - runTriggers - run all and after', +function(test, done) { + var store = []; + var triggers = MakeTriggers(2, store); + Triggers.runTriggers(triggers, null, null, function() { + test.equal(store, [0, 1]); + done(); + }); +}); + +Tinytest.addAsync( +'Triggers - runTriggers - redirect with url', +function(test, done) { + var store = []; + var url = "http://google.com"; + var triggers = MakeTriggers(2, store); + triggers.splice(1, 0, function(context, redirect) { + redirect(url); + }); + + Triggers.runTriggers(triggers, null, function(u) { + test.equal(store, [0]); + test.equal(u, url); + done(); + }, null); +}); + +Tinytest.addAsync( +'Triggers - runTriggers - redirect without url', +function(test, done) { + var store = []; + var url = "http://google.com"; + var triggers = MakeTriggers(2, store); + triggers.splice(1, 0, function(context, redirect) { + try { + redirect(); + } catch(ex) { + test.isTrue(/requires an URL/.test(ex.message)); + test.equal(store, [0]); + done(); + } + }); + + Triggers.runTriggers(triggers, null, null, null); +}); + +Tinytest.addAsync( +'Triggers - runTriggers - redirect in a different event loop', +function(test, done) { + var store = []; + var url = "http://google.com"; + var triggers = MakeTriggers(2, store); + var doneCalled = false; + + triggers.splice(1, 0, function(context, redirect) { + setTimeout(function() { + try { + redirect(url); + } catch(ex) { + test.isTrue(/sync/.test(ex.message)); + test.equal(store, [0, 1]); + test.isTrue(doneCalled); + done(); + } + }, 0); + }); + + Triggers.runTriggers(triggers, null, null, function() { + doneCalled = true; + }); +}); + +Tinytest.addAsync( +'Triggers - runTriggers - redirect called multiple times', +function(test, done) { + var store = []; + var url = "http://google.com"; + var triggers = MakeTriggers(2, store); + var redirectCalled = false; + + triggers.splice(1, 0, function(context, redirect) { + redirect(url); + try { + redirect(url); + } catch(ex) { + test.isTrue(/already redirected/.test(ex.message)); + test.equal(store, [0]); + test.isTrue(redirectCalled); + done(); + } + }); + + Triggers.runTriggers(triggers, null, function() { + redirectCalled = true; + }, null); +}); + +Tinytest.addAsync( +'Triggers - runTriggers - stop callback', +function(test, done) { + var store = []; + var triggers = MakeTriggers(2, store); + triggers.splice(1, 0, function(context, redirect, stop) { + stop(); + }); + + Triggers.runTriggers(triggers, null, null, function() { + store.push(2); + }); + + test.equal(store, [0]); + done(); +}); + + +Tinytest.addAsync( +'Triggers - runTriggers - get context', +function(test, done) { + var context = {}; + var trigger = function(c) { + test.equal(c, context); + done(); + }; + + Triggers.runTriggers([trigger], context, function() {}, function() {}); +}); + +Tinytest.addAsync( +'Triggers - createRouteBoundTriggers - matching trigger', +function(test, done) { + var context = {route: {name: "abc"}}; + var redirect = function() {}; + + var trigger = function(c, r) { + test.equal(c, context); + test.equal(r, redirect); + done(); + }; + + var triggers = Triggers.createRouteBoundTriggers([trigger], ["abc"]); + triggers[0](context, redirect); +}); + +Tinytest.addAsync( +'Triggers - createRouteBoundTriggers - multiple matching triggers', +function(test, done) { + var context = {route: {name: "abc"}}; + var redirect = function() {}; + var doneCount = 0; + + var trigger = function(c, r) { + test.equal(c, context); + test.equal(r, redirect); + doneCount++; + }; + + var triggers = Triggers.createRouteBoundTriggers([trigger, trigger], ["abc"]); + triggers[0](context, redirect); + triggers[1](context, redirect); + + test.equal(doneCount, 2); + done(); +}); + +Tinytest.addAsync( +'Triggers - createRouteBoundTriggers - no matching trigger', +function(test, done) { + var context = {route: {name: "some-other-route"}}; + var redirect = function() {}; + var doneCount = 0; + + var trigger = function(c, r) { + test.equal(c, context); + test.equal(r, redirect); + doneCount++; + }; + + var triggers = Triggers.createRouteBoundTriggers([trigger], ["abc"]); + triggers[0](context, redirect); + + test.equal(doneCount, 0); + done(); +}); + +Tinytest.addAsync( +'Triggers - createRouteBoundTriggers - negate logic', +function(test, done) { + var context = {route: {name: "some-other-route"}}; + var redirect = function() {}; + var doneCount = 0; + + var trigger = function(c, r) { + test.equal(c, context); + test.equal(r, redirect); + doneCount++; + }; + + var triggers = Triggers.createRouteBoundTriggers([trigger], ["abc"], true); + triggers[0](context, redirect); + + test.equal(doneCount, 1); + done(); +}); + +Tinytest.addAsync( +'Triggers - applyFilters - no filters', +function(test, done) { + var original = []; + test.equal(Triggers.applyFilters(original), original); + done(); +}); + +Tinytest.addAsync( +'Triggers - applyFilters - single trigger to array', +function(test, done) { + var original = function() {}; + test.equal(Triggers.applyFilters(original)[0], original); + done(); +}); + +Tinytest.addAsync( +'Triggers - applyFilters - only and except both', +function(test, done) { + var original = []; + try { + Triggers.applyFilters(original, {only: [], except: []}); + } catch(ex) { + test.isTrue(/only and except/.test(ex.message)); + done(); + } +}); + +Tinytest.addAsync( +'Triggers - applyFilters - only is not an array', +function(test, done) { + var original = []; + try { + Triggers.applyFilters(original, {only: "name"}); + } catch(ex) { + test.isTrue(/to be an array/.test(ex.message)); + done(); + } +}); + +Tinytest.addAsync( +'Triggers - applyFilters - except is not an array', +function(test, done) { + var original = []; + try { + Triggers.applyFilters(original, {except: "name"}); + } catch(ex) { + test.isTrue(/to be an array/.test(ex.message)); + done(); + } +}); + +Tinytest.addAsync( +'Triggers - applyFilters - unsupported filter', +function(test, done) { + var original = []; + try { + Triggers.applyFilters(original, {wowFilter: []}); + } catch(ex) { + test.isTrue(/not supported/.test(ex.message)); + done(); + } +}); + +Tinytest.addAsync( +'Triggers - applyFilters - just only filter', +function(test, done) { + var bounded = Triggers.applyFilters(done, {only: ["abc"]}); + bounded[0]({route: {name: "abc"}}); +}); + +Tinytest.addAsync( +'Triggers - applyFilters - just except filter', +function(test, done) { + var bounded = Triggers.applyFilters(done, {except: ["abc"]}); + bounded[0]({route: {name: "some-other"}}); +}); + +function MakeTriggers(count, store) { + var triggers = []; + + function addTrigger(no) { + triggers.push(function() { + store.push(no); + }); + } + + for(var lc=0; lc<count; lc++) { + addTrigger(lc); + } + return triggers; +} \ No newline at end of file diff --git a/packages/kadira-flow-router/test/common/fast_render_route.js b/packages/kadira-flow-router/test/common/fast_render_route.js new file mode 100644 index 000000000..d56f1c066 --- /dev/null +++ b/packages/kadira-flow-router/test/common/fast_render_route.js @@ -0,0 +1,48 @@ +FastRenderColl = new Mongo.Collection('fast-render-coll'); + +FlowRouter.route('/the-fast-render-route', { + subscriptions: function() { + this.register('data', Meteor.subscribe('fast-render-data')); + } +}); + +FlowRouter.route('/the-fast-render-route-params/:id', { + subscriptions: function(params, queryParams) { + this.register('data', Meteor.subscribe('fast-render-data-params', params, queryParams)); + } +}); + +FlowRouter.route('/no-fast-render', { + subscriptions: function() { + if(Meteor.isClient) { + this.register('data', Meteor.subscribe('fast-render-data')); + } + } +}); + +var frGroup = FlowRouter.group({ + prefix: "/fr" +}); + +frGroup.route("/have-fr", { + subscriptions: function() { + this.register('data', Meteor.subscribe('fast-render-data')); + } +}); + +if(Meteor.isServer) { + if(!FastRenderColl.findOne()) { + FastRenderColl.insert({_id: "one", aa: 10}); + FastRenderColl.insert({_id: "two", aa: 20}); + } + + Meteor.publish('fast-render-data', function() { + return FastRenderColl.find({}, {sort: {aa: -1}}); + }); + + Meteor.publish('fast-render-data-params', function(params, queryParams) { + var fields = {params: params, queryParams: queryParams}; + this.added('fast-render-coll', 'one', fields); + this.ready(); + }); +} \ No newline at end of file diff --git a/packages/kadira-flow-router/test/common/group.spec.js b/packages/kadira-flow-router/test/common/group.spec.js new file mode 100644 index 000000000..e6e799cbc --- /dev/null +++ b/packages/kadira-flow-router/test/common/group.spec.js @@ -0,0 +1,16 @@ +Tinytest.add('Common - Group - expose group options', function (test) { + var pathDef = "/" + Random.id(); + var name = Random.id(); + var data = {aa: 10}; + var layout = 'blah'; + + var group = FlowRouter.group({ + name: name, + prefix: '/admin', + layout: layout, + someData: data + }); + + test.equal(group.options.someData, data); + test.equal(group.options.layout, layout); +}); diff --git a/packages/kadira-flow-router/test/common/route.spec.js b/packages/kadira-flow-router/test/common/route.spec.js new file mode 100644 index 000000000..9e4c7e5a7 --- /dev/null +++ b/packages/kadira-flow-router/test/common/route.spec.js @@ -0,0 +1,15 @@ +Router = FlowRouter.Router; + +Tinytest.addAsync('Common - Route - expose route options', function (test, next) { + var pathDef = "/" + Random.id(); + var name = Random.id(); + var data = {aa: 10}; + + FlowRouter.route(pathDef, { + name: name, + someData: data + }); + + test.equal(FlowRouter._routesMap[name].options.someData, data); + next(); +}); diff --git a/packages/kadira-flow-router/test/common/router.addons.spec.js b/packages/kadira-flow-router/test/common/router.addons.spec.js new file mode 100644 index 000000000..f50787341 --- /dev/null +++ b/packages/kadira-flow-router/test/common/router.addons.spec.js @@ -0,0 +1,30 @@ +Router = FlowRouter.Router; + +Tinytest.addAsync('Common - Addons - onRouteRegister basic usage', function (test, done) { + var name = Random.id(); + var customField = Random.id(); + var pathDef = '/' + name; + + FlowRouter.onRouteRegister(function(route) { + test.equal(route, { + pathDef: pathDef, + + // Route.path is deprecated and will be removed in 3.0 + path: pathDef, + + name: name, + options: {customField: customField} + }); + FlowRouter._onRouteCallbacks = []; + done(); + }); + + FlowRouter.route(pathDef, { + name: name, + action: function() {}, + subscriptions: function() {}, + triggersEnter: function() {}, + triggersExit: function() {}, + customField: customField + }); +}); diff --git a/packages/kadira-flow-router/test/common/router.path.spec.js b/packages/kadira-flow-router/test/common/router.path.spec.js new file mode 100644 index 000000000..92f0881ec --- /dev/null +++ b/packages/kadira-flow-router/test/common/router.path.spec.js @@ -0,0 +1,135 @@ +Router = FlowRouter.Router; + +Tinytest.addAsync('Common - Router - validate path definition', function (test, next) { + // path must start with '/' + try { + FlowRouter.route(Random.id()); + } catch(ex) { + next(); + } +}); + +Tinytest.add('Common - Router - path - generic', function (test) { + var pathDef = "/blog/:blogId/some/:name"; + var fields = { + blogId: "1001", + name: "superb" + }; + var expectedPath = "/blog/1001/some/superb"; + + var path = FlowRouter.path(pathDef, fields); + test.equal(path, expectedPath); +}); + +Tinytest.add('Common - Router - path - queryParams', function (test) { + var pathDef = "/blog/:blogId/some/:name"; + var fields = { + blogId: "1001", + name: "superb" + }; + + var queryParams = { + aa: "100", + bb: "200" + }; + + var expectedPath = "/blog/1001/some/superb?aa=100&bb=200"; + + var path = FlowRouter.path(pathDef, fields, queryParams); + test.equal(path, expectedPath); +}); + +Tinytest.add('Common - Router - path - just queryParams', function (test) { + var pathDef = "/blog/abc"; + var queryParams = { + aa: "100", + bb: "200" + }; + + var expectedPath = "/blog/abc?aa=100&bb=200"; + + var path = FlowRouter.path(pathDef, null, queryParams); + test.equal(path, expectedPath); +}); + + +Tinytest.add('Common - Router - path - missing fields', function (test) { + var pathDef = "/blog/:blogId/some/:name"; + var fields = { + blogId: "1001", + }; + var expectedPath = "/blog/1001/some"; + + var path = FlowRouter.path(pathDef, fields); + test.equal(path, expectedPath); +}); + +Tinytest.add('Common - Router - path - no fields', function (test) { + var pathDef = "/blog/blogId/some/name"; + var path = FlowRouter.path(pathDef); + test.equal(path, pathDef); +}); + +Tinytest.add('Common - Router - path - complex route', function (test) { + var pathDef = "/blog/:blogId/some/:name(\\d*)+"; + var fields = { + blogId: "1001", + name: 20 + }; + var expectedPath = "/blog/1001/some/20"; + + var path = FlowRouter.path(pathDef, fields); + test.equal(path, expectedPath); +}); + +Tinytest.add('Common - Router - path - optional last param missing', function (test) { + var pathDef = "/blog/:blogId/some/:name?"; + var fields = { + blogId: "1001" + }; + var expectedPath = "/blog/1001/some"; + + var path = FlowRouter.path(pathDef, fields); + test.equal(path, expectedPath); +}); + +Tinytest.add('Common - Router - path - optional last param exists', function (test) { + var pathDef = "/blog/:blogId/some/:name?"; + var fields = { + blogId: "1001", + name: 20 + }; + var expectedPath = "/blog/1001/some/20"; + + var path = FlowRouter.path(pathDef, fields); + test.equal(path, expectedPath); +}); + +Tinytest.add('Common - Router - path - remove trailing slashes', function (test) { + var pathDef = "/blog/:blogId/some/:name//"; + var fields = { + blogId: "1001", + name: "superb" + }; + var expectedPath = "/blog/1001/some/superb"; + + var path = FlowRouter.path(pathDef, fields); + test.equal(path, expectedPath); +}); + +Tinytest.add('Common - Router - path - handle multiple slashes', function (test) { + var pathDef = "/blog///some/hi////"; + var expectedPath = "/blog/some/hi"; + + var path = FlowRouter.path(pathDef); + test.equal(path, expectedPath); +}); + +Tinytest.add('Common - Router - path - keep the root slash', function (test) { + var pathDef = "/"; + var fields = {}; + var expectedPath = "/"; + + var path = FlowRouter.path(pathDef, fields); + test.equal(path, expectedPath); +}); diff --git a/packages/kadira-flow-router/test/common/router.url.spec.js b/packages/kadira-flow-router/test/common/router.url.spec.js new file mode 100644 index 000000000..0dbf46324 --- /dev/null +++ b/packages/kadira-flow-router/test/common/router.url.spec.js @@ -0,0 +1,11 @@ +Tinytest.add('Common - Router - url - generic', function (test) { + var pathDef = "/blog/:blogId/some/:name"; + var fields = { + blogId: "1001", + name: "superb" + }; + var expectedUrl = Meteor.absoluteUrl('blog/1001/some/superb'); + + var path = FlowRouter.url(pathDef, fields); + test.equal(path, expectedUrl); +}); diff --git a/packages/kadira-flow-router/test/server/_helpers.js b/packages/kadira-flow-router/test/server/_helpers.js new file mode 100644 index 000000000..c7538851b --- /dev/null +++ b/packages/kadira-flow-router/test/server/_helpers.js @@ -0,0 +1,38 @@ +Meteor.publish('foo', function () { + this.ready(); +}); + +Meteor.publish('fooNotReady', function () { +}); + +Meteor.publish('bar', function () { + this.ready(); +}); + +// use this only to test global subs +Meteor.publish('baz', function () { + this.ready(); +}); + +Meteor.publish('bazNotReady', function () { +}); + +Meteor.publish('readyness', function (doIt) { + if(doIt) { + this.ready(); + } +}); + +InjectData = Package['meteorhacks:inject-data'].InjectData; +var urlResolve = Npm.require('url').resolve; +GetFRData = function GetFRData(path) { + var url = urlResolve(process.env.ROOT_URL, path); + // FastRender only servers if there is a accept header with html in it + var options = { + headers: {'accept': 'html'} + }; + var res = HTTP.get(url, options); + + var encodedData = res.content.match(/data">(.*)<\/script/)[1]; + return InjectData._decode(encodedData)['fast-render-data']; +} \ No newline at end of file diff --git a/packages/kadira-flow-router/test/server/plugins/fast_render.js b/packages/kadira-flow-router/test/server/plugins/fast_render.js new file mode 100644 index 000000000..1ec77866a --- /dev/null +++ b/packages/kadira-flow-router/test/server/plugins/fast_render.js @@ -0,0 +1,35 @@ +Tinytest.add('Server - Fast Render - fast render supported route', function (test) { + var expectedFastRenderCollData = [ + [{_id: "two", aa: 20}, {_id: "one", aa: 10}] + ]; + + var data = GetFRData('/the-fast-render-route'); + test.equal(data.collectionData['fast-render-coll'], expectedFastRenderCollData); +}); + +Tinytest.add('Server - Fast Render - fast render supported route with params', function (test) { + var expectedFastRenderCollData = [ + [{ + _id: "one", + params: {id: 'the-id'}, + queryParams: {aa: "20"} + }] + ]; + + var data = GetFRData('/the-fast-render-route-params/the-id?aa=20'); + test.equal(data.collectionData['fast-render-coll'], expectedFastRenderCollData); +}); + +Tinytest.add('Server - Fast Render - no fast render supported route', function (test) { + var data = GetFRData('/no-fast-render'); + test.equal(data.collectionData, {}); +}); + +Tinytest.add('Server - Fast Render - with group routes', function (test) { + var expectedFastRenderCollData = [ + [{_id: "two", aa: 20}, {_id: "one", aa: 10}] + ]; + + var data = GetFRData('/fr/have-fr'); + test.equal(data.collectionData['fast-render-coll'], expectedFastRenderCollData); +}); \ No newline at end of file diff --git a/packages/wekan-accounts-cas/cas_client.js b/packages/wekan-accounts-cas/cas_client.js index 9790fb22a..ca9288ae2 100644 --- a/packages/wekan-accounts-cas/cas_client.js +++ b/packages/wekan-accounts-cas/cas_client.js @@ -93,8 +93,6 @@ Meteor.loginWithCas = function(options, callback) { }; var openCenteredPopup = function(url, width, height) { - // #FIXME screenX and outerWidth are often different units on mobile screen or high DPI - // see https://developer.mozilla.org/en-US/docs/Web/API/Window/devicePixelRatio var screenX = typeof window.screenX !== 'undefined' ? window.screenX : window.screenLeft; var screenY = typeof window.screenY !== 'undefined' diff --git a/packages/wekan-accounts-cas/cas_server.js b/packages/wekan-accounts-cas/cas_server.js index f8bb2d894..2e8edef2c 100644 --- a/packages/wekan-accounts-cas/cas_server.js +++ b/packages/wekan-accounts-cas/cas_server.js @@ -1,8 +1,9 @@ "use strict"; -import https from 'https'; -import url from 'url'; -import xml2js from 'xml2js'; +const Fiber = Npm.require('fibers'); +const https = Npm.require('https'); +const url = Npm.require('url'); +const xmlParser = Npm.require('xml2js'); // Library class CAS { @@ -60,7 +61,7 @@ class CAS { console.log(error); callback(undefined, false); } else { - xml2js.parseString(response, (err, result) => { + xmlParser.parseString(response, (err, result) => { if (err) { console.log('Bad response format.'); callback({message: 'Bad response format. XML could not parse it'}); @@ -119,16 +120,20 @@ let _userData = {}; //RoutePolicy.declare('/_cas/', 'network'); // Listen to incoming OAuth http requests -WebApp.connectHandlers.use(Meteor.bindEnvironment((req, res, next) => { - middleware(req, res, next); -})); +WebApp.connectHandlers.use((req, res, next) => { + // Need to create a Fiber since we're using synchronous http calls and nothing + // else is wrapping this in a fiber automatically + + Fiber(() => { + middleware(req, res, next); + }).run(); +}); const middleware = (req, res, next) => { // Make sure to catch any exceptions because otherwise we'd crash // the runner - let redirectUrl; try { - const urlParsed = url.parse(req.url, true); + urlParsed = url.parse(req.url, true); // Getting the ticket (if it's defined in GET-params) // If no ticket, then request will continue down the default @@ -145,7 +150,7 @@ const middleware = (req, res, next) => { } const serviceUrl = Meteor.absoluteUrl(urlParsed.href.replace(/^\//g, '')).replace(/([&?])ticket=[^&]+[&]?/g, '$1').replace(/[?&]+$/g, ''); - redirectUrl = serviceUrl;//.replace(/([&?])casToken=[^&]+[&]?/g, '$1').replace(/[?&]+$/g, ''); + const redirectUrl = serviceUrl;//.replace(/([&?])casToken=[^&]+[&]?/g, '$1').replace(/[?&]+$/g, ''); // get auth token const credentialToken = query.casToken; @@ -201,7 +206,7 @@ const casValidate = (req, ticket, token, service, callback) => { * Register a server-side login handle. * It is call after Accounts.callLoginMethod() is call from client. */ - Accounts.registerLoginHandler(async (options) => { + Accounts.registerLoginHandler((options) => { if (!options.cas) return undefined; @@ -247,13 +252,13 @@ const casValidate = (req, ticket, token, service, callback) => { if (attrs.debug) { console.log(`CAS response : ${JSON.stringify(result)}`); } - let user = await Meteor.users.findOneAsync({ 'username': options.username }); + let user = Meteor.users.findOne({ 'username': options.username }); if (! user) { if (attrs.debug) { console.log(`Creating user account ${JSON.stringify(options)}`); } - const userId = await Accounts.insertUserDoc({}, options); - user = await Meteor.users.findOneAsync(userId); + const userId = Accounts.insertUserDoc({}, options); + user = Meteor.users.findOne(userId); } if (attrs.debug) { console.log(`Using user account ${JSON.stringify(user)}`); @@ -262,7 +267,7 @@ const casValidate = (req, ticket, token, service, callback) => { }); const _hasCredential = (credentialToken) => { - return Object.prototype.hasOwnProperty.call(_casCredentialTokens, credentialToken); + return _.has(_casCredentialTokens, credentialToken); } /* diff --git a/packages/wekan-accounts-cas/package.js b/packages/wekan-accounts-cas/package.js index 8cf749829..314d17c17 100644 --- a/packages/wekan-accounts-cas/package.js +++ b/packages/wekan-accounts-cas/package.js @@ -1,17 +1,17 @@ Package.describe({ summary: "CAS support for accounts", - version: "0.2.0", + version: "0.1.0", name: "wekan-accounts-cas", git: "https://github.com/wekan/meteor-accounts-cas" }); Package.onUse(function(api) { - api.use('ecmascript'); api.use('routepolicy', 'server'); api.use('webapp', 'server'); api.use('accounts-base', ['client', 'server']); // Export Accounts (etc) to packages using this one. api.imply('accounts-base', ['client', 'server']); + api.use('underscore'); api.addFiles('cas_client.js', 'web.browser'); api.addFiles('cas_client_cordova.js', 'web.cordova'); api.addFiles('cas_server.js', 'server'); diff --git a/packages/wekan-accounts-lockout/package.js b/packages/wekan-accounts-lockout/package.js index 7909e667f..7f1a64b21 100644 --- a/packages/wekan-accounts-lockout/package.js +++ b/packages/wekan-accounts-lockout/package.js @@ -2,7 +2,7 @@ Package.describe({ name: 'wekan-accounts-lockout', - version: '1.1.0', + version: '1.0.0', summary: 'Meteor package for locking user accounts and stopping brute force attacks', git: 'https://github.com/lucasantoniassi/meteor-accounts-lockout.git', documentation: 'README.md', diff --git a/packages/wekan-accounts-lockout/src/knownUser.js b/packages/wekan-accounts-lockout/src/knownUser.js index 2f4d2012e..81558e1b8 100644 --- a/packages/wekan-accounts-lockout/src/knownUser.js +++ b/packages/wekan-accounts-lockout/src/knownUser.js @@ -9,12 +9,12 @@ class KnownUser { this.settings = settings; } - async startup() { + startup() { if (!(this.unchangedSettings instanceof Function)) { this.updateSettings(); } - await this.scheduleUnlocksForLockedAccounts(); - await KnownUser.unlockAccountsIfLockoutAlreadyExpired(); + this.scheduleUnlocksForLockedAccounts(); + KnownUser.unlockAccountsIfLockoutAlreadyExpired(); this.hookIntoAccounts(); } @@ -49,7 +49,7 @@ class KnownUser { } } - async scheduleUnlocksForLockedAccounts() { + scheduleUnlocksForLockedAccounts() { const lockedAccountsCursor = Meteor.users.find( { 'services.accounts-lockout.unlockTime': { @@ -63,7 +63,7 @@ class KnownUser { }, ); const currentTime = Number(new Date()); - for await (const user of lockedAccountsCursor) { + lockedAccountsCursor.forEach((user) => { let lockDuration = KnownUser.unlockTime(user) - currentTime; if (lockDuration >= this.settings.lockoutPeriod) { lockDuration = this.settings.lockoutPeriod * 1000; @@ -75,10 +75,10 @@ class KnownUser { KnownUser.unlockAccount.bind(null, user._id), lockDuration, ); - } + }); } - static async unlockAccountsIfLockoutAlreadyExpired() { + static unlockAccountsIfLockoutAlreadyExpired() { const currentTime = Number(new Date()); const query = { 'services.accounts-lockout.unlockTime': { @@ -91,7 +91,7 @@ class KnownUser { 'services.accounts-lockout.failedAttempts': 0, }, }; - await Meteor.users.updateAsync(query, data); + Meteor.users.update(query, data); } hookIntoAccounts() { @@ -100,7 +100,7 @@ class KnownUser { } - async validateLoginAttempt(loginInfo) { + validateLoginAttempt(loginInfo) { if ( // don't interrupt non-password logins loginInfo.type !== 'password' || @@ -130,12 +130,12 @@ class KnownUser { const canReset = (currentTime - firstFailedAttempt) > (1000 * this.settings.failureWindow); if (canReset) { failedAttempts = 1; - await KnownUser.resetAttempts(failedAttempts, userId); + KnownUser.resetAttempts(failedAttempts, userId); } const canIncrement = failedAttempts < this.settings.failuresBeforeLockout; if (canIncrement) { - await KnownUser.incrementAttempts(failedAttempts, userId); + KnownUser.incrementAttempts(failedAttempts, userId); } const maxAttemptsAllowed = this.settings.failuresBeforeLockout; @@ -147,7 +147,7 @@ class KnownUser { KnownUser.tooManyAttempts(duration); } if (failedAttempts === maxAttemptsAllowed) { - await this.setNewUnlockTime(failedAttempts, userId); + this.setNewUnlockTime(failedAttempts, userId); let duration = this.settings.lockoutPeriod; duration = Math.ceil(duration); @@ -161,7 +161,7 @@ class KnownUser { ); } - static async resetAttempts( + static resetAttempts( failedAttempts, userId, ) { @@ -174,10 +174,10 @@ class KnownUser { 'services.accounts-lockout.firstFailedAttempt': currentTime, }, }; - await Meteor.users.updateAsync(query, data); + Meteor.users.update(query, data); } - static async incrementAttempts( + static incrementAttempts( failedAttempts, userId, ) { @@ -189,10 +189,10 @@ class KnownUser { 'services.accounts-lockout.lastFailedAttempt': currentTime, }, }; - await Meteor.users.updateAsync(query, data); + Meteor.users.update(query, data); } - async setNewUnlockTime( + setNewUnlockTime( failedAttempts, userId, ) { @@ -206,14 +206,14 @@ class KnownUser { 'services.accounts-lockout.unlockTime': newUnlockTime, }, }; - await Meteor.users.updateAsync(query, data); + Meteor.users.update(query, data); Meteor.setTimeout( KnownUser.unlockAccount.bind(null, userId), this.settings.lockoutPeriod * 1000, ); } - static async onLogin(loginInfo) { + static onLogin(loginInfo) { if (loginInfo.type !== 'password') { return; } @@ -225,7 +225,7 @@ class KnownUser { 'services.accounts-lockout.failedAttempts': 0, }, }; - await Meteor.users.updateAsync(query, data); + Meteor.users.update(query, data); } static incorrectPassword( @@ -306,7 +306,7 @@ class KnownUser { return firstFailedAttempt || 0; } - static async unlockAccount(userId) { + static unlockAccount(userId) { const query = { _id: userId }; const data = { $unset: { @@ -314,7 +314,7 @@ class KnownUser { 'services.accounts-lockout.failedAttempts': 0, }, }; - await Meteor.users.updateAsync(query, data); + Meteor.users.update(query, data); } } diff --git a/packages/wekan-accounts-lockout/src/unknownUser.js b/packages/wekan-accounts-lockout/src/unknownUser.js index 9226969e6..443507c82 100644 --- a/packages/wekan-accounts-lockout/src/unknownUser.js +++ b/packages/wekan-accounts-lockout/src/unknownUser.js @@ -13,12 +13,12 @@ class UnknownUser { this.settings = settings; } - async startup() { + startup() { if (!(this.settings instanceof Function)) { this.updateSettings(); } - await this.scheduleUnlocksForLockedAccounts(); - await this.unlockAccountsIfLockoutAlreadyExpired(); + this.scheduleUnlocksForLockedAccounts(); + this.unlockAccountsIfLockoutAlreadyExpired(); this.hookIntoAccounts(); } @@ -53,7 +53,7 @@ class UnknownUser { } } - async scheduleUnlocksForLockedAccounts() { + scheduleUnlocksForLockedAccounts() { const lockedAccountsCursor = this.AccountsLockoutCollection.find( { 'services.accounts-lockout.unlockTime': { @@ -67,8 +67,8 @@ class UnknownUser { }, ); const currentTime = Number(new Date()); - for await (const connection of lockedAccountsCursor) { - let lockDuration = await this.unlockTime(connection) - currentTime; + lockedAccountsCursor.forEach((connection) => { + let lockDuration = this.unlockTime(connection) - currentTime; if (lockDuration >= this.settings.lockoutPeriod) { lockDuration = this.settings.lockoutPeriod * 1000; } @@ -79,10 +79,10 @@ class UnknownUser { this.unlockAccount.bind(this, connection.clientAddress), lockDuration, ); - } + }); } - async unlockAccountsIfLockoutAlreadyExpired() { + unlockAccountsIfLockoutAlreadyExpired() { const currentTime = Number(new Date()); const query = { 'services.accounts-lockout.unlockTime': { @@ -95,7 +95,7 @@ class UnknownUser { 'services.accounts-lockout.failedAttempts': 0, }, }; - await this.AccountsLockoutCollection.updateAsync(query, data); + this.AccountsLockoutCollection.update(query, data); } hookIntoAccounts() { @@ -103,7 +103,7 @@ class UnknownUser { Accounts.onLogin(this.onLogin.bind(this)); } - async validateLoginAttempt(loginInfo) { + validateLoginAttempt(loginInfo) { // don't interrupt non-password logins if ( loginInfo.type !== 'password' || @@ -120,20 +120,20 @@ class UnknownUser { } const clientAddress = loginInfo.connection.clientAddress; - const unlockTime = await this.unlockTime(loginInfo.connection); - let failedAttempts = 1 + await this.failedAttempts(loginInfo.connection); - const firstFailedAttempt = await this.firstFailedAttempt(loginInfo.connection); + const unlockTime = this.unlockTime(loginInfo.connection); + let failedAttempts = 1 + this.failedAttempts(loginInfo.connection); + const firstFailedAttempt = this.firstFailedAttempt(loginInfo.connection); const currentTime = Number(new Date()); const canReset = (currentTime - firstFailedAttempt) > (1000 * this.settings.failureWindow); if (canReset) { failedAttempts = 1; - await this.resetAttempts(failedAttempts, clientAddress); + this.resetAttempts(failedAttempts, clientAddress); } const canIncrement = failedAttempts < this.settings.failuresBeforeLockout; if (canIncrement) { - await this.incrementAttempts(failedAttempts, clientAddress); + this.incrementAttempts(failedAttempts, clientAddress); } const maxAttemptsAllowed = this.settings.failuresBeforeLockout; @@ -145,7 +145,7 @@ class UnknownUser { UnknownUser.tooManyAttempts(duration); } if (failedAttempts === maxAttemptsAllowed) { - await this.setNewUnlockTime(failedAttempts, clientAddress); + this.setNewUnlockTime(failedAttempts, clientAddress); let duration = this.settings.lockoutPeriod; duration = Math.ceil(duration); @@ -159,7 +159,7 @@ class UnknownUser { ); } - async resetAttempts( + resetAttempts( failedAttempts, clientAddress, ) { @@ -172,10 +172,10 @@ class UnknownUser { 'services.accounts-lockout.firstFailedAttempt': currentTime, }, }; - await this.AccountsLockoutCollection.upsertAsync(query, data); + this.AccountsLockoutCollection.upsert(query, data); } - async incrementAttempts( + incrementAttempts( failedAttempts, clientAddress, ) { @@ -187,10 +187,10 @@ class UnknownUser { 'services.accounts-lockout.lastFailedAttempt': currentTime, }, }; - await this.AccountsLockoutCollection.upsertAsync(query, data); + this.AccountsLockoutCollection.upsert(query, data); } - async setNewUnlockTime( + setNewUnlockTime( failedAttempts, clientAddress, ) { @@ -204,14 +204,14 @@ class UnknownUser { 'services.accounts-lockout.unlockTime': newUnlockTime, }, }; - await this.AccountsLockoutCollection.upsertAsync(query, data); + this.AccountsLockoutCollection.upsert(query, data); Meteor.setTimeout( this.unlockAccount.bind(this, clientAddress), this.settings.lockoutPeriod * 1000, ); } - async onLogin(loginInfo) { + onLogin(loginInfo) { if (loginInfo.type !== 'password') { return; } @@ -223,7 +223,7 @@ class UnknownUser { 'services.accounts-lockout.failedAttempts': 0, }, }; - await this.AccountsLockoutCollection.updateAsync(query, data); + this.AccountsLockoutCollection.update(query, data); } static userNotFound( @@ -264,14 +264,14 @@ class UnknownUser { return unknownUsers || false; } - async findOneByConnection(connection) { - return await this.AccountsLockoutCollection.findOneAsync({ + findOneByConnection(connection) { + return this.AccountsLockoutCollection.findOne({ clientAddress: connection.clientAddress, }); } - async unlockTime(connection) { - connection = await this.findOneByConnection(connection); + unlockTime(connection) { + connection = this.findOneByConnection(connection); let unlockTime; try { unlockTime = connection.services['accounts-lockout'].unlockTime; @@ -281,8 +281,8 @@ class UnknownUser { return unlockTime || 0; } - async failedAttempts(connection) { - connection = await this.findOneByConnection(connection); + failedAttempts(connection) { + connection = this.findOneByConnection(connection); let failedAttempts; try { failedAttempts = connection.services['accounts-lockout'].failedAttempts; @@ -292,8 +292,8 @@ class UnknownUser { return failedAttempts || 0; } - async lastFailedAttempt(connection) { - connection = await this.findOneByConnection(connection); + lastFailedAttempt(connection) { + connection = this.findOneByConnection(connection); let lastFailedAttempt; try { lastFailedAttempt = connection.services['accounts-lockout'].lastFailedAttempt; @@ -303,8 +303,8 @@ class UnknownUser { return lastFailedAttempt || 0; } - async firstFailedAttempt(connection) { - connection = await this.findOneByConnection(connection); + firstFailedAttempt(connection) { + connection = this.findOneByConnection(connection); let firstFailedAttempt; try { firstFailedAttempt = connection.services['accounts-lockout'].firstFailedAttempt; @@ -314,7 +314,7 @@ class UnknownUser { return firstFailedAttempt || 0; } - async unlockAccount(clientAddress) { + unlockAccount(clientAddress) { const query = { clientAddress }; const data = { $unset: { @@ -322,7 +322,7 @@ class UnknownUser { 'services.accounts-lockout.failedAttempts': 0, }, }; - await this.AccountsLockoutCollection.updateAsync(query, data); + this.AccountsLockoutCollection.update(query, data); } } diff --git a/packages/wekan-accounts-sandstorm/client.js b/packages/wekan-accounts-sandstorm/client.js index 5f3f208ea..61c176fcb 100644 --- a/packages/wekan-accounts-sandstorm/client.js +++ b/packages/wekan-accounts-sandstorm/client.js @@ -86,7 +86,7 @@ function loginWithSandstorm(connection, apiHost, apiToken) { var sendXhr = function () { if (!waiting) return; // Method call finished. - var headers = {"Content-Type": "application/x-sandstorm-login-token"}; + headers = {"Content-Type": "application/x-sandstorm-login-token"}; var testInfo = localStorage.sandstormTestUserInfo; if (testInfo) { @@ -120,20 +120,16 @@ function loginWithSandstorm(connection, apiHost, apiToken) { // Send the token in an HTTP POST request which on the server side will allow us to receive the // Sandstorm headers. - fetch(postUrl, { - method: 'POST', - headers: headers, - body: token - }).then(function (response) { - if (!response.ok) { - throw new Error(response.statusText); - } - }).catch(function (error) { - console.error("couldn't get /.sandstorm-login:", error); + HTTP.post(postUrl, + {content: token, headers: headers}, + function (error, result) { + if (error) { + console.error("couldn't get /.sandstorm-login:", error); - if (waiting) { - // Try again in a second. - Meteor.setTimeout(sendXhr, 1000); + if (waiting) { + // Try again in a second. + Meteor.setTimeout(sendXhr, 1000); + } } }); }; diff --git a/packages/wekan-accounts-sandstorm/package.js b/packages/wekan-accounts-sandstorm/package.js index 8e8a06f64..b3972324b 100644 --- a/packages/wekan-accounts-sandstorm/package.js +++ b/packages/wekan-accounts-sandstorm/package.js @@ -21,7 +21,7 @@ Package.describe({ summary: "Login service for Sandstorm.io applications", - version: "0.9.0", + version: "0.8.0", name: "wekan-accounts-sandstorm", git: "https://github.com/sandstorm-io/meteor-accounts-sandstorm.git" }); @@ -30,7 +30,7 @@ Package.onUse(function(api) { api.use('random', ['client', 'server']); api.use('accounts-base', ['client', 'server'], {weak: true}); api.use('webapp', 'server'); - api.use('fetch', 'client'); + api.use('http', 'client'); api.use('tracker', 'client'); api.use('reactive-var', 'client'); api.use('check', 'server'); diff --git a/packages/wekan-accounts-sandstorm/server.js b/packages/wekan-accounts-sandstorm/server.js index 7de97c949..032549692 100644 --- a/packages/wekan-accounts-sandstorm/server.js +++ b/packages/wekan-accounts-sandstorm/server.js @@ -43,13 +43,17 @@ if (__meteor_runtime_config__.SANDSTORM) { }); } + var Future = Npm.require("fibers/future"); + + var inMeteor = Meteor.bindEnvironment(function (callback) { + callback(); + }); + var logins = {}; // Maps tokens to currently-waiting login method calls. if (Package["accounts-base"]) { - Meteor.startup(async () => { - await Meteor.users.createIndexAsync("services.sandstorm.id", {unique: 1, sparse: 1}); - }); + Meteor.users.createIndex("services.sandstorm.id", {unique: 1, sparse: 1}); } Meteor.onConnection(function (connection) { @@ -77,24 +81,22 @@ if (__meteor_runtime_config__.SANDSTORM) { }); Meteor.methods({ - async loginWithSandstorm(token) { + loginWithSandstorm: function (token) { check(token, String); - const loginPromise = new Promise((resolve, reject) => { - const timeout = setTimeout(() => { - reject(new Meteor.Error("timeout", "Gave up waiting for login rendezvous XHR.")); - }, 10000); + var future = new Future(); - logins[token] = { resolve, reject, timeout }; - }); + logins[token] = future; + + var timeout = setTimeout(function () { + future.throw(new Meteor.Error("timeout", "Gave up waiting for login rendezvous XHR.")); + }, 10000); var info; try { - info = await loginPromise; + info = future.wait(); } finally { - if (logins[token] && logins[token].timeout) { - clearTimeout(logins[token].timeout); - } + clearTimeout(timeout); delete logins[token]; } @@ -124,79 +126,85 @@ if (__meteor_runtime_config__.SANDSTORM) { return next(); }); - async function readAll(stream) { - return new Promise((resolve, reject) => { - const chunks = []; - stream.on("data", function (chunk) { - chunks.push(chunk.toString()); - }); - stream.on("error", function (err) { - reject(err); - }); - stream.on("end", function () { - resolve(chunks.join("")); - }); + function readAll(stream) { + var future = new Future(); + + var chunks = []; + stream.on("data", function (chunk) { + chunks.push(chunk.toString()); }); + stream.on("error", function (err) { + future.throw(err); + }); + stream.on("end", function () { + future.return(); + }); + + future.wait(); + + return chunks.join(""); } - var handlePostToken = Meteor.bindEnvironment(async function (req, res) { - try { - // Note that cross-origin POSTs cannot set arbitrary Content-Types without explicit CORS - // permission, so this effectively prevents XSRF. - if (req.headers["content-type"].split(";")[0].trim() !== "application/x-sandstorm-login-token") { - throw new Error("wrong Content-Type for .sandstorm-login: " + req.headers["content-type"]); - } - - var token = await readAll(req); - - var loginEntry = logins[token]; - if (!loginEntry) { - throw new Error("no current login request matching token"); - } - - var permissions = req.headers["x-sandstorm-permissions"]; - if (permissions && permissions !== "") { - permissions = permissions.split(","); - } else { - permissions = []; - } - - var sandstormInfo = { - id: req.headers["x-sandstorm-user-id"] || null, - name: decodeURIComponent(req.headers["x-sandstorm-username"]), - permissions: permissions, - picture: req.headers["x-sandstorm-user-picture"] || null, - preferredHandle: req.headers["x-sandstorm-preferred-handle"] || null, - pronouns: req.headers["x-sandstorm-user-pronouns"] || null, - }; - - var userInfo = {sandstorm: sandstormInfo}; - if (Package["accounts-base"]) { - if (sandstormInfo.id) { - // The user is logged into Sandstorm. Create a Meteor account for them, or find the - // existing one, and record the user ID. - var login = await Package["accounts-base"].Accounts.updateOrCreateUserFromExternalService( - "sandstorm", sandstormInfo, {profile: {name: sandstormInfo.name}}); - userInfo.userId = login.userId; - } else { - userInfo.userId = null; + var handlePostToken = Meteor.bindEnvironment(function (req, res) { + inMeteor(function () { + try { + // Note that cross-origin POSTs cannot set arbitrary Content-Types without explicit CORS + // permission, so this effectively prevents XSRF. + if (req.headers["content-type"].split(";")[0].trim() !== "application/x-sandstorm-login-token") { + throw new Error("wrong Content-Type for .sandstorm-login: " + req.headers["content-type"]); } - } else { - // Since the app isn't using regular Meteor accounts, we can define Meteor.userId() - // however we want. - userInfo.userId = sandstormInfo.id; - } - userInfo.sessionId = req.headers["x-sandstorm-session-id"] || null; - userInfo.tabId = req.headers["x-sandstorm-tab-id"] || null; - loginEntry.resolve(userInfo); - res.writeHead(204, {}); - res.end(); - } catch (err) { - res.writeHead(500, { - "Content-Type": "text/plain" - }); - res.end(err.stack); - } + var token = readAll(req); + + var future = logins[token]; + if (!future) { + throw new Error("no current login request matching token"); + } + + var permissions = req.headers["x-sandstorm-permissions"]; + if (permissions && permissions !== "") { + permissions = permissions.split(","); + } else { + permissions = []; + } + + var sandstormInfo = { + id: req.headers["x-sandstorm-user-id"] || null, + name: decodeURIComponent(req.headers["x-sandstorm-username"]), + permissions: permissions, + picture: req.headers["x-sandstorm-user-picture"] || null, + preferredHandle: req.headers["x-sandstorm-preferred-handle"] || null, + pronouns: req.headers["x-sandstorm-user-pronouns"] || null, + }; + + var userInfo = {sandstorm: sandstormInfo}; + if (Package["accounts-base"]) { + if (sandstormInfo.id) { + // The user is logged into Sandstorm. Create a Meteor account for them, or find the + // existing one, and record the user ID. + var login = Package["accounts-base"].Accounts.updateOrCreateUserFromExternalService( + "sandstorm", sandstormInfo, {profile: {name: sandstormInfo.name}}); + userInfo.userId = login.userId; + } else { + userInfo.userId = null; + } + } else { + // Since the app isn't using regular Meteor accounts, we can define Meteor.userId() + // however we want. + userInfo.userId = sandstormInfo.id; + } + + userInfo.sessionId = req.headers["x-sandstorm-session-id"] || null; + userInfo.tabId = req.headers["x-sandstorm-tab-id"] || null; + future.return(userInfo); + res.writeHead(204, {}); + res.end(); + } catch (err) { + res.writeHead(500, { + "Content-Type": "text/plain" + }); + res.end(err.stack); + } + }); }); } diff --git a/packages/wekan-fullcalendar/README.md b/packages/wekan-fullcalendar/README.md index 68e117624..1a81e4764 100644 --- a/packages/wekan-fullcalendar/README.md +++ b/packages/wekan-fullcalendar/README.md @@ -1,51 +1,57 @@ -[FullCalendar](https://fullcalendar.io/) packaged for Wekan as a Blaze wrapper. +[FullCalendar](http://fullcalendar.io/) JQuery plugin packaged for Meteor 1.0 -### Installation +### Instalation ### -This package is bundled in Wekan (`wekan-fullcalendar`). + meteor add rzymek:fullcalendar -### Usage +### Usage ### -```handlebars -{{> fullcalendar calendarOptions}} -``` + {{> fullcalendar }} -Options can be passed directly from a helper: +Options to FullCalendar can be passed as attributes: -```js -Template.example.helpers({ - calendarOptions() { - return { - id: 'myCalendar', - initialView: 'dayGridMonth', - headerToolbar: { - left: 'title today prev,next', - center: 'timeGridDay,timeGridWeek,dayGridMonth,listMonth', - right: '', - }, - events(fetchInfo, successCallback) { - successCallback([]); - }, - }; - }, -}); -``` + {{> fullcalendar defaultView='agendaWeek'}} + +If you want to have options defined in JS (or have them reactive), you can do: -### Compatibility notes + <template name="example"> + {{>fullcalendar options}} + </template> -- Uses FullCalendar v5 modules (`@fullcalendar/*`) and no longer uses jQuery plugin APIs. -- Legacy options are mapped for compatibility: - - `defaultView` -> `initialView` - - `header` -> `headerToolbar` + Template.example.helpers({ + options: function() { + return { + defaultView: 'basicWeek' + }; + } + }); -### Refetching events +To access the `.fullcalendar` method assign an `id` or a `class` first -If you provide an `id`, the wrapper stores the calendar instance on the container -element as `_wekanCalendar`: + {{> fullcalendar id="myCalendar" ...}} -```js -const el = document.getElementById('myCalendar'); -if (el && el._wekanCalendar) { - el._wekanCalendar.refetchEvents(); -} -``` +Then you can for example do + + $('#myCalendar').fullCalendar('refetchEvents'); + +### Updating fullcalendar ### + +To update fullcalendar version run + + ./update.sh +This will update to the newest fullcalendar's tag. +To update to a specific version do + + ./update.sh 2.2.6 + +If you want me to publish a new package version just [create an issue](https://github.com/rzymek/meteor-fullcalendar/issues/new). +In case you can't wait to use a new fullcalendar version in your project, you can update the package locally: + + cd your_meteor_project + mkdir -p packages + git clone https://github.com/rzymek/meteor-fullcalendar packages/rzymek:fullcalendar + ./packages/rzymek:fullcalendar/update.sh + +After the desired version gets published just remove the local package: + + rm -r packages/rzymek:fullcalendar diff --git a/packages/wekan-fullcalendar/fullcalendar/fullcalendar.css b/packages/wekan-fullcalendar/fullcalendar/fullcalendar.css index 055efc4f2..1600d948e 100644 --- a/packages/wekan-fullcalendar/fullcalendar/fullcalendar.css +++ b/packages/wekan-fullcalendar/fullcalendar/fullcalendar.css @@ -467,7 +467,7 @@ temporary rendered events). /* resizer (touch devices) */ .fc-h-event.fc-selected .fc-resizer { /* 8x8 little dot */ - border-radius: 0.4ch; + border-radius: 4px; border-width: 1px; width: 6px; height: 6px; @@ -1145,7 +1145,7 @@ be a descendant of the grid when it is being dragged. height: 8px; overflow: hidden; line-height: 8px; - + font-size: 11px; font-family: monospace; text-align: center; cursor: s-resize; } diff --git a/packages/wekan-fullcalendar/package.js b/packages/wekan-fullcalendar/package.js index d0f922f2c..38e8d64fc 100644 --- a/packages/wekan-fullcalendar/package.js +++ b/packages/wekan-fullcalendar/package.js @@ -1,30 +1,21 @@ Package.describe({ - name: 'wekan-fullcalendar', - summary: 'Full-sized drag & drop event calendar (jQuery plugin)', - version: '5.11.5', - git: 'https://github.com/fullcalendar/fullcalendar.git', -}); - -Npm.depends({ - '@fullcalendar/core': '5.11.5', - '@fullcalendar/daygrid': '5.11.5', - '@fullcalendar/interaction': '5.11.5', - '@fullcalendar/list': '5.11.5', - '@fullcalendar/timegrid': '5.11.5', + name: 'wekan-fullcalendar', + summary: "Full-sized drag & drop event calendar (jQuery plugin)", + version: "3.10.5", + git: "https://github.com/fullcalendar/fullcalendar.git" }); Package.onUse(function(api) { - api.versionsFrom(['2.16', '3.0']); - api.use(['ecmascript', 'templating', 'tracker'], 'client'); - api.addFiles( - [ - '.npm/package/node_modules/@fullcalendar/common/main.min.css', - '.npm/package/node_modules/@fullcalendar/daygrid/main.min.css', - '.npm/package/node_modules/@fullcalendar/timegrid/main.min.css', - '.npm/package/node_modules/@fullcalendar/list/main.min.css', - 'template.html', - 'template.js', - ], - 'client', - ); + api.use([ + 'momentjs:moment', + 'templating' + ], 'client'); + api.addFiles([ + 'template.html', + 'template.js', + 'fullcalendar/fullcalendar.js', + 'fullcalendar/fullcalendar.css', + 'fullcalendar/locale-all.js', + 'fullcalendar/gcal.js', + ], 'client'); }); diff --git a/packages/wekan-fullcalendar/template.js b/packages/wekan-fullcalendar/template.js index cc4b13b7d..a41d21d3d 100644 --- a/packages/wekan-fullcalendar/template.js +++ b/packages/wekan-fullcalendar/template.js @@ -1,86 +1,11 @@ -import { Template } from 'meteor/templating'; +window.moment = moment; -const FullCalendarCore = require('@fullcalendar/core/main.cjs.js'); -const FullCalendarDayGrid = require('@fullcalendar/daygrid/main.cjs.js'); -const FullCalendarInteraction = require('@fullcalendar/interaction/main.cjs.js'); -const FullCalendarList = require('@fullcalendar/list/main.cjs.js'); -const FullCalendarTimeGrid = require('@fullcalendar/timegrid/main.cjs.js'); -const FullCalendarLocalesAll = require('@fullcalendar/core/locales-all.js'); - -Template.fullcalendar.onRendered(function () { - const instance = this; - const container = this.find('div'); - - this.autorunHandle = this.autorun(() => { - const data = Template.currentData() || {}; - let preservedViewType = null; - let preservedDate = null; - - if (!container) { - return; +Template.fullcalendar.rendered = function() { + var div = this.$(this.firstNode); + if(this.data != null) { + //jquery takes care of undefined values, no need to check here + div.attr('id', this.data.id); + div.addClass(this.data.class); } - - container.id = data.id || ''; - container.className = data.class || ''; - - const options = { ...data }; - delete options.id; - delete options.class; - if (options.defaultView && !options.initialView) { - options.initialView = options.defaultView; - } - delete options.defaultView; - if (options.header && !options.headerToolbar) { - options.headerToolbar = options.header; - } - delete options.header; - - if (!options.locales && FullCalendarLocalesAll && FullCalendarLocalesAll.default) { - options.locales = FullCalendarLocalesAll.default; - } - - if (instance.calendar) { - // Keep the user's current view/date when reactive data updates. - if (instance.calendar.view && instance.calendar.view.type) { - preservedViewType = instance.calendar.view.type; - } - if (instance.calendar.getDate) { - preservedDate = instance.calendar.getDate(); - } - instance.calendar.destroy(); - instance.calendar = null; - } - - if (preservedViewType && !options.initialView) { - options.initialView = preservedViewType; - } - if (preservedDate && !options.initialDate) { - options.initialDate = preservedDate; - } - - instance.calendar = new FullCalendarCore.Calendar(container, { - plugins: [ - FullCalendarDayGrid.default, - FullCalendarInteraction.default, - FullCalendarList.default, - FullCalendarTimeGrid.default, - ], - ...options, - }); - - // Allow callers to manually access and refetch without jQuery plugin API. - container._wekanCalendar = instance.calendar; - instance.calendar.render(); - }); -}); - -Template.fullcalendar.onDestroyed(function () { - if (this.autorunHandle) { - this.autorunHandle.stop(); - this.autorunHandle = null; - } - if (this.calendar) { - this.calendar.destroy(); - this.calendar = null; - } -}); + div.fullCalendar(this.data); +}; diff --git a/packages/wekan-ldap/package.js b/packages/wekan-ldap/package.js index 6e7b367c7..57a2ede35 100644 --- a/packages/wekan-ldap/package.js +++ b/packages/wekan-ldap/package.js @@ -1,6 +1,6 @@ Package.describe({ name: 'wekan-ldap', - version: '0.1.0', + version: '0.0.2', // Brief, one-line summary of the package. summary: 'Basic meteor login with ldap', // URL to the Git repository containing the source code for this package. @@ -12,6 +12,7 @@ Package.describe({ Package.onUse(function(api) { + api.use('yasaricli:slugify'); api.use('ecmascript'); api.use('underscore'); api.use('sha'); @@ -19,13 +20,8 @@ Package.onUse(function(api) { api.use('accounts-base', 'server'); api.use('accounts-password', 'server'); - api.use('quave:synced-cron', 'server'); + api.use('percolate:synced-cron', 'server'); api.addFiles('client/loginHelper.js', 'client'); api.mainModule('server/index.js', 'server'); }); - -Npm.depends({ - 'ldapts': '4.2.6', - 'limax': '4.1.0' -}); diff --git a/packages/wekan-ldap/server/ldap.js b/packages/wekan-ldap/server/ldap.js index 94dfe7020..428196423 100644 --- a/packages/wekan-ldap/server/ldap.js +++ b/packages/wekan-ldap/server/ldap.js @@ -1,4 +1,4 @@ -import { Client } from 'ldapts'; +import ldapjs from 'ldapjs'; import { Log } from 'meteor/logging'; // copied from https://github.com/ldapjs/node-ldapjs/blob/a113953e0d91211eb945d2a3952c84b7af6de41c/lib/filters/index.js#L167 @@ -18,14 +18,10 @@ function escapedToHex (str) { } } -// Convert hex string to LDAP escaped binary filter value -// e.g. "0102ff" -> "\\01\\02\\ff" -function hexToLdapEscaped(hex) { - return hex.match(/.{2}/g).map(h => '\\' + h).join(''); -} - export default class LDAP { constructor() { + this.ldapjs = ldapjs; + this.connected = false; this.options = { @@ -77,9 +73,34 @@ export default class LDAP { } } - async connect() { + connectSync(...args) { + if (!this._connectSync) { + this._connectSync = Meteor.wrapAsync(this.connectAsync, this); + } + return this._connectSync(...args); + } + + searchAllSync(...args) { + + if (!this._searchAllSync) { + this._searchAllSync = Meteor.wrapAsync(this.searchAllAsync, this); + } + return this._searchAllSync(...args); + } + + connectAsync(callback) { Log.info('Init setup'); + let replied = false; + + const connectionOptions = { + url : `${this.options.host}:${this.options.port}`, + timeout : this.options.timeout, + connectTimeout: this.options.connect_timeout, + idleTimeout : this.options.idle_timeout, + reconnect : this.options.Reconnect, + }; + const tlsOptions = { rejectUnauthorized: this.options.reject_unauthorized, }; @@ -99,114 +120,81 @@ export default class LDAP { tlsOptions.ca = ca; } - let url; if (this.options.encryption === 'ssl') { - url = `ldaps://${this.options.host}:${this.options.port}`; + connectionOptions.url = `ldaps://${connectionOptions.url}`; + connectionOptions.tlsOptions = tlsOptions; } else { - url = `ldap://${this.options.host}:${this.options.port}`; + connectionOptions.url = `ldap://${connectionOptions.url}`; } - Log.info(`Connecting ${url}`); + Log.info(`Connecting ${connectionOptions.url}`); + Log.debug(`connectionOptions ${JSON.stringify(connectionOptions)}`); - const clientOptions = { - url, - timeout : this.options.timeout, - connectTimeout: this.options.connect_timeout, - strictDN : false, - }; + this.client = ldapjs.createClient(connectionOptions); - if (this.options.encryption === 'ssl') { - clientOptions.tlsOptions = tlsOptions; - } + this.bindSync = Meteor.wrapAsync(this.client.bind, this.client); - Log.debug(`clientOptions ${JSON.stringify(clientOptions)}`); + this.client.on('error', (error) => { + Log.error(`connection ${error}`); + if (replied === false) { + replied = true; + callback(error, null); + } + }); - this.client = new Client(clientOptions); + this.client.on('idle', () => { + Log.info('Idle'); + this.disconnect(); + }); + + this.client.on('close', () => { + Log.info('Closed'); + }); if (this.options.encryption === 'tls') { - // Set host parameter for tls.connect which is used by starttls. This shouldn't be needed in newer nodejs versions (e.g v5.6.0). + // Set host parameter for tls.connect which is used by ldapjs starttls. This shouldn't be needed in newer nodejs versions (e.g v5.6.0). // https://github.com/RocketChat/Rocket.Chat/issues/2035 + // https://github.com/mcavage/node-ldapjs/issues/349 tlsOptions.host = this.options.host; Log.info('Starting TLS'); Log.debug(`tlsOptions ${JSON.stringify(tlsOptions)}`); - await this.client.startTLS(tlsOptions); - Log.info('TLS connected'); - } - - this.connected = true; - } - - async bind(dn, password) { - await this.client.bind(dn, password); - } - - getBufferAttributes() { - const fields = []; - let uidField = this.constructor.settings_get('LDAP_UNIQUE_IDENTIFIER_FIELD'); - if (uidField && uidField !== '') { - fields.push(...uidField.replace(/\s/g, '').split(',')); - } - let searchField = this.constructor.settings_get('LDAP_USER_SEARCH_FIELD'); - if (searchField && searchField !== '') { - fields.push(...searchField.replace(/\s/g, '').split(',')); - } - return fields; - } - - async searchAll(BaseDN, options) { - const searchOptions = { - filter: options.filter, - scope : options.scope || 'sub', - }; - - if (options.attributes) { - searchOptions.attributes = options.attributes; - } - - if (options.sizeLimit) { - searchOptions.sizeLimit = options.sizeLimit; - } - - if (options.paged) { - searchOptions.paged = { - pageSize: options.paged.pageSize || 250, - }; - } - - // Request unique identifier fields as Buffers so that - // getLdapUserUniqueID() in sync.js can call .toString('hex') - const bufferAttributes = this.getBufferAttributes(); - if (bufferAttributes.length > 0) { - searchOptions.explicitBufferAttributes = bufferAttributes; - } - - const { searchEntries } = await this.client.search(BaseDN, searchOptions); - - Log.info(`Search result count ${searchEntries.length}`); - return searchEntries.map((entry) => this.extractLdapEntryData(entry)); - } - - extractLdapEntryData(entry) { - const values = { - _raw: {}, - }; - - for (const key of Object.keys(entry)) { - const value = entry[key]; - values._raw[key] = value; - - if (!['thumbnailPhoto', 'jpegPhoto'].includes(key)) { - if (value instanceof Buffer) { - values[key] = value.toString(); - } else { - values[key] = value; + this.client.starttls(tlsOptions, null, (error, response) => { + if (error) { + Log.error(`TLS connection ${JSON.stringify(error)}`); + if (replied === false) { + replied = true; + callback(error, null); + } + return; } - } + + Log.info('TLS connected'); + this.connected = true; + if (replied === false) { + replied = true; + callback(null, response); + } + }); + } else { + this.client.on('connect', (response) => { + Log.info('LDAP connected'); + this.connected = true; + if (replied === false) { + replied = true; + callback(null, response); + } + }); } - return values; + setTimeout(() => { + if (replied === false) { + Log.error(`connection time out ${connectionOptions.connectTimeout}`); + replied = true; + callback(new Error('Timeout')); + } + }, connectionOptions.connectTimeout); } getUserFilter(username) { @@ -220,9 +208,7 @@ export default class LDAP { } } - // Escape the username to prevent LDAP injection - const escapedUsername = escapedToHex(username); - const usernameFilter = this.options.User_Search_Field.split(',').map((item) => `(${item}=${escapedUsername})`); + const usernameFilter = this.options.User_Search_Field.split(',').map((item) => `(${item}=${username})`); if (usernameFilter.length === 0) { Log.error('LDAP_LDAP_User_Search_Field not defined'); @@ -235,7 +221,7 @@ export default class LDAP { return `(&${filter.join('')})`; } - async bindUserIfNecessary(username, password) { + bindUserIfNecessary(username, password) { if (this.domainBinded === true) { return; @@ -248,22 +234,20 @@ export default class LDAP { /* if SimpleAuth is configured, the BaseDN is not needed */ if (!this.options.BaseDN && !this.options.AD_Simple_Auth) throw new Error('BaseDN is not provided'); - // Escape the username to prevent LDAP injection in DN construction - const escapedUsername = escapedToHex(username); var userDn = ""; if (this.options.AD_Simple_Auth === true || this.options.AD_Simple_Auth === 'true') { - userDn = `${escapedUsername}@${this.options.Default_Domain}`; + userDn = `${username}@${this.options.Default_Domain}`; } else { - userDn = `${this.options.User_Authentication_Field}=${escapedUsername},${this.options.BaseDN}`; + userDn = `${this.options.User_Authentication_Field}=${username},${this.options.BaseDN}`; } Log.info(`Binding with User ${userDn}`); - await this.bind(userDn, password); + this.bindSync(userDn, password); this.domainBinded = true; } - async bindIfNecessary() { + bindIfNecessary() { if (this.domainBinded === true) { return; } @@ -274,12 +258,12 @@ export default class LDAP { Log.info(`Binding UserDN ${this.options.Authentication_UserDN}`); - await this.bind(this.options.Authentication_UserDN, this.options.Authentication_Password); + this.bindSync(this.options.Authentication_UserDN, this.options.Authentication_Password); this.domainBinded = true; } - async searchUsers(username) { - await this.bindIfNecessary(); + searchUsersSync(username, page) { + this.bindIfNecessary(); const searchOptions = { filter : this.getUserFilter(username), scope : this.options.User_Search_Scope || 'sub', @@ -290,7 +274,8 @@ export default class LDAP { if (this.options.Search_Page_Size > 0) { searchOptions.paged = { - pageSize: this.options.Search_Page_Size, + pageSize : this.options.Search_Page_Size, + pagePause: !!page, }; } @@ -298,22 +283,35 @@ export default class LDAP { Log.debug(`searchOptions ${searchOptions}`); Log.debug(`BaseDN ${this.options.BaseDN}`); - return await this.searchAll(this.options.BaseDN, searchOptions); + if (page) { + return this.searchAllPaged(this.options.BaseDN, searchOptions, page); + } + + return this.searchAllSync(this.options.BaseDN, searchOptions); } - async getUserById(id, attribute) { - await this.bindIfNecessary(); + getUserByIdSync(id, attribute) { + this.bindIfNecessary(); const Unique_Identifier_Field = this.constructor.settings_get('LDAP_UNIQUE_IDENTIFIER_FIELD').split(','); - const escapedValue = hexToLdapEscaped(id); let filter; if (attribute) { - filter = `(${attribute}=${escapedValue})`; + filter = new this.ldapjs.filters.EqualityFilter({ + attribute, + value: Buffer.from(id, 'hex'), + }); } else { - const filters = Unique_Identifier_Field.map((item) => `(${item}=${escapedValue})`); - filter = `(|${filters.join('')})`; + const filters = []; + Unique_Identifier_Field.forEach((item) => { + filters.push(new this.ldapjs.filters.EqualityFilter({ + attribute: item, + value : Buffer.from(id, 'hex'), + })); + }); + + filter = new this.ldapjs.filters.OrFilter({ filters }); } const searchOptions = { @@ -322,10 +320,10 @@ export default class LDAP { }; Log.info(`Searching by id ${id}`); - Log.debug(`search filter ${searchOptions.filter}`); + Log.debug(`search filter ${searchOptions.filter.toString()}`); Log.debug(`BaseDN ${this.options.BaseDN}`); - const result = await this.searchAll(this.options.BaseDN, searchOptions); + const result = this.searchAllSync(this.options.BaseDN, searchOptions); if (!Array.isArray(result) || result.length === 0) { return; @@ -338,8 +336,8 @@ export default class LDAP { return result[0]; } - async getUserByUsername(username) { - await this.bindIfNecessary(); + getUserByUsernameSync(username) { + this.bindIfNecessary(); const searchOptions = { filter: this.getUserFilter(username), @@ -350,7 +348,7 @@ export default class LDAP { Log.debug(`searchOptions ${searchOptions}`); Log.debug(`BaseDN ${this.options.BaseDN}`); - const result = await this.searchAll(this.options.BaseDN, searchOptions); + const result = this.searchAllSync(this.options.BaseDN, searchOptions); if (!Array.isArray(result) || result.length === 0) { return; @@ -363,7 +361,7 @@ export default class LDAP { return result[0]; } - async getUserGroups(username, ldapUser) { + getUserGroups(username, ldapUser) { if (!this.options.group_filter_enabled) { return true; } @@ -383,16 +381,14 @@ export default class LDAP { filter.push(')'); - // Escape the username to prevent LDAP injection - const escapedUsername = escapedToHex(username); const searchOptions = { - filter: filter.join('').replace(/#{username}/g, escapedUsername).replace("\\", "\\\\"), + filter: filter.join('').replace(/#{username}/g, username).replace("\\", "\\\\"), scope : 'sub', }; Log.debug(`Group list filter LDAP: ${searchOptions.filter}`); - const result = await this.searchAll(this.options.BaseDN, searchOptions); + const result = this.searchAllSync(this.options.BaseDN, searchOptions); if (!Array.isArray(result) || result.length === 0) { return []; @@ -408,12 +404,12 @@ export default class LDAP { } - async isUserInGroup(username, ldapUser) { + isUserInGroup(username, ldapUser) { if (!this.options.group_filter_enabled) { return true; } - const grps = await this.getUserGroups(username, ldapUser); + const grps = this.getUserGroups(username, ldapUser); const filter = ['(&']; @@ -433,16 +429,14 @@ export default class LDAP { } filter.push(')'); - // Escape the username to prevent LDAP injection - const escapedUsername = escapedToHex(username); const searchOptions = { - filter: filter.join('').replace(/#{username}/g, escapedUsername).replace("\\", "\\\\"), + filter: filter.join('').replace(/#{username}/g, username).replace("\\", "\\\\"), scope : 'sub', }; Log.debug(`Group filter LDAP: ${searchOptions.filter}`); - const result = await this.searchAll(this.options.BaseDN, searchOptions); + const result = this.searchAllSync(this.options.BaseDN, searchOptions); if (!Array.isArray(result) || result.length === 0) { return false; @@ -450,14 +444,142 @@ export default class LDAP { return true; } - async auth(dn, password) { + extractLdapEntryData(entry) { + const values = { + _raw: entry.raw, + }; + + Object.keys(values._raw).forEach((key) => { + const value = values._raw[key]; + + if (!['thumbnailPhoto', 'jpegPhoto'].includes(key)) { + if (value instanceof Buffer) { + values[key] = value.toString(); + } else { + values[key] = value; + } + } + }); + + return values; + } + + searchAllPaged(BaseDN, options, page) { + this.bindIfNecessary(); + + const processPage = ({ entries, title, end, next }) => { + Log.info(title); + // Force LDAP idle to wait the record processing + this.client._updateIdle(true); + page(null, entries, { + end, next: () => { + // Reset idle timer + this.client._updateIdle(); + next && next(); + } + }); + }; + + this.client.search(BaseDN, options, (error, res) => { + if (error) { + Log.error(error); + page(error); + return; + } + + res.on('error', (error) => { + Log.error(error); + page(error); + return; + }); + + let entries = []; + + const internalPageSize = options.paged && options.paged.pageSize > 0 ? options.paged.pageSize * 2 : 500; + + res.on('searchEntry', (entry) => { + entries.push(this.extractLdapEntryData(entry)); + + if (entries.length >= internalPageSize) { + processPage({ + entries, + title: 'Internal Page', + end : false, + }); + entries = []; + } + }); + + res.on('page', (result, next) => { + if (!next) { + this.client._updateIdle(true); + processPage({ + entries, + title: 'Final Page', + end : true, + }); + } else if (entries.length) { + Log.info('Page'); + processPage({ + entries, + title: 'Page', + end : false, + next, + }); + entries = []; + } + }); + + res.on('end', () => { + if (entries.length) { + processPage({ + entries, + title: 'Final Page', + end : true, + }); + entries = []; + } + }); + }); + } + + searchAllAsync(BaseDN, options, callback) { + this.bindIfNecessary(); + + this.client.search(BaseDN, options, (error, res) => { + if (error) { + Log.error(error); + callback(error); + return; + } + + res.on('error', (error) => { + Log.error(error); + callback(error); + return; + }); + + const entries = []; + + res.on('searchEntry', (entry) => { + entries.push(this.extractLdapEntryData(entry)); + }); + + res.on('end', () => { + Log.info(`Search result count ${entries.length}`); + callback(null, entries); + }); + }); + } + + authSync(dn, password) { Log.info(`Authenticating ${dn}`); try { if (password === '') { throw new Error('Password is not provided'); } - await this.bind(dn, password); + this.bindSync(dn, password); Log.info(`Authenticated ${dn}`); return true; } catch (error) { @@ -467,14 +589,10 @@ export default class LDAP { } } - async disconnect() { + disconnect() { this.connected = false; this.domainBinded = false; Log.info('Disconecting'); - try { - await this.client.unbind(); - } catch (error) { - Log.debug('Error during disconnect', error); - } + this.client.unbind(); } } diff --git a/packages/wekan-ldap/server/loginHandler.js b/packages/wekan-ldap/server/loginHandler.js index 29ff386fa..090ef9da3 100644 --- a/packages/wekan-ldap/server/loginHandler.js +++ b/packages/wekan-ldap/server/loginHandler.js @@ -25,7 +25,7 @@ function fallbackDefaultAccountSystem(bind, username, password) { return Accounts._runLoginHandlers(bind, loginRequest); } -Accounts.registerLoginHandler('ldap', async function(loginRequest) { +Accounts.registerLoginHandler('ldap', function(loginRequest) { if (!loginRequest.ldap || !loginRequest.ldapOptions) { return undefined; } @@ -42,27 +42,27 @@ Accounts.registerLoginHandler('ldap', async function(loginRequest) { try { - await ldap.connect(); + ldap.connectSync(); if (!!LDAP.settings_get('LDAP_USER_AUTHENTICATION')) { - await ldap.bindUserIfNecessary(loginRequest.username, loginRequest.ldapPass); - ldapUser = (await ldap.searchUsers(loginRequest.username))[0]; + ldap.bindUserIfNecessary(loginRequest.username, loginRequest.ldapPass); + ldapUser = ldap.searchUsersSync(loginRequest.username)[0]; } else { - const users = await ldap.searchUsers(loginRequest.username); + const users = ldap.searchUsersSync(loginRequest.username); if (users.length !== 1) { log_info('Search returned', users.length, 'record(s) for', loginRequest.username); throw new Error('User not Found'); } - if (await ldap.isUserInGroup(loginRequest.username, users[0])) { + if (ldap.isUserInGroup(loginRequest.username, users[0])) { ldapUser = users[0]; } else { throw new Error('User not in a valid group'); } - if (await ldap.auth(users[0].dn, loginRequest.ldapPass) !== true) { + if (ldap.authSync(users[0].dn, loginRequest.ldapPass) !== true) { ldapUser = null; log_info('Wrong password for', loginRequest.username) } @@ -96,7 +96,7 @@ Accounts.registerLoginHandler('ldap', async function(loginRequest) { log_info('Querying user'); log_debug('userQuery', userQuery); - user = await Meteor.users.findOneAsync(userQuery); + user = Meteor.users.findOne(userQuery); } // Attempt to find user by username @@ -137,7 +137,7 @@ Accounts.registerLoginHandler('ldap', async function(loginRequest) { log_debug('userQuery', userQuery); - user = await Meteor.users.findOneAsync(userQuery); + user = Meteor.users.findOne(userQuery); } // Attempt to find user by e-mail address only @@ -159,7 +159,7 @@ Accounts.registerLoginHandler('ldap', async function(loginRequest) { log_debug('userQuery', userQuery); - user = await Meteor.users.findOneAsync(userQuery); + user = Meteor.users.findOne(userQuery); } @@ -182,15 +182,15 @@ Accounts.registerLoginHandler('ldap', async function(loginRequest) { if (LDAP.settings_get('LDAP_SYNC_ADMIN_STATUS') === true) { log_debug('Updating admin status'); const targetGroups = LDAP.settings_get('LDAP_SYNC_ADMIN_GROUPS').split(','); - const groups = (await ldap.getUserGroups(username, ldapUser)).filter((value) => targetGroups.includes(value)); + const groups = ldap.getUserGroups(username, ldapUser).filter((value) => targetGroups.includes(value)); user.isAdmin = groups.length > 0; - await Meteor.users.updateAsync({_id: user._id}, {$set: {isAdmin: user.isAdmin}}); + Meteor.users.update({_id: user._id}, {$set: {isAdmin: user.isAdmin}}); } if( LDAP.settings_get('LDAP_SYNC_GROUP_ROLES') === true ) { log_debug('Updating Groups/Roles'); - const groups = await ldap.getUserGroups(username, ldapUser); + const groups = ldap.getUserGroups(username, ldapUser); if( groups.length > 0 ) { Roles.setUserRoles(user._id, groups ); @@ -198,12 +198,12 @@ Accounts.registerLoginHandler('ldap', async function(loginRequest) { } } - await Meteor.users.updateAsync(user._id, update_data ); + Meteor.users.update(user._id, update_data ); - await syncUserData(user, ldapUser); + syncUserData(user, ldapUser); if (LDAP.settings_get('LDAP_LOGIN_FALLBACK') === true) { - await Accounts.setPasswordAsync(user._id, loginRequest.ldapPass, {logout: false}); + Accounts.setPassword(user._id, loginRequest.ldapPass, {logout: false}); } return { @@ -224,19 +224,19 @@ Accounts.registerLoginHandler('ldap', async function(loginRequest) { loginRequest.ldapPass = undefined; } - const result = await addLdapUser(ldapUser, username, loginRequest.ldapPass); + const result = addLdapUser(ldapUser, username, loginRequest.ldapPass); if (LDAP.settings_get('LDAP_SYNC_ADMIN_STATUS') === true) { log_debug('Updating admin status'); const targetGroups = LDAP.settings_get('LDAP_SYNC_ADMIN_GROUPS').split(','); - const groups = (await ldap.getUserGroups(username, ldapUser)).filter((value) => targetGroups.includes(value)); + const groups = ldap.getUserGroups(username, ldapUser).filter((value) => targetGroups.includes(value)); result.isAdmin = groups.length > 0; - await Meteor.users.updateAsync({_id: result.userId}, {$set: {isAdmin: result.isAdmin}}); + Meteor.users.update({_id: result.userId}, {$set: {isAdmin: result.isAdmin}}); } if( LDAP.settings_get('LDAP_SYNC_GROUP_ROLES') === true ) { - const groups = await ldap.getUserGroups(username, ldapUser); + const groups = ldap.getUserGroups(username, ldapUser); if( groups.length > 0 ) { Roles.setUserRoles(result.userId, groups ); log_info(`Set roles to:${ groups.join(',')}`); diff --git a/packages/wekan-ldap/server/sync.js b/packages/wekan-ldap/server/sync.js index 802fcebf7..efe7e35f3 100644 --- a/packages/wekan-ldap/server/sync.js +++ b/packages/wekan-ldap/server/sync.js @@ -1,6 +1,5 @@ import _ from 'underscore'; -import { SyncedCron } from 'meteor/quave:synced-cron'; -import limax from 'limax'; +import SyncedCron from 'meteor/percolate:synced-cron'; import LDAP from './ldap'; import { log_debug, log_info, log_warn, log_error } from './logger'; @@ -21,7 +20,7 @@ export function slug(text) { if (LDAP.settings_get('LDAP_UTF8_NAMES_SLUGIFY') !== true) { return text; } - text = limax(text, { separator: '.' }); + text = slugify(text, '.'); return text.replace(/[^0-9a-z-_.]/g, ''); } @@ -231,7 +230,7 @@ export function getDataToSyncUserData(ldapUser, user) { } -export async function syncUserData(user, ldapUser) { +export function syncUserData(user, ldapUser) { log_info('Syncing user data'); log_debug('user', {'email': user.email, '_id': user._id}); // log_debug('ldapUser', ldapUser.object); @@ -240,7 +239,7 @@ export async function syncUserData(user, ldapUser) { const username = slug(getLdapUsername(ldapUser)); if (user && user._id && username !== user.username) { log_info('Syncing user username', user.username, '->', username); - await Meteor.users.findOneAsync({ _id: user._id }, { $set: { username }}); + Meteor.users.findOne({ _id: user._id }, { $set: { username }}); } } @@ -249,7 +248,7 @@ export async function syncUserData(user, ldapUser) { log_debug('fullname=',fullname); if (user && user._id && fullname !== '') { log_info('Syncing user fullname:', fullname); - await Meteor.users.updateAsync({ _id: user._id }, { $set: { 'profile.fullname' : fullname, }}); + Meteor.users.update({ _id: user._id }, { $set: { 'profile.fullname' : fullname, }}); } } @@ -259,7 +258,7 @@ export async function syncUserData(user, ldapUser) { if (user && user._id && email !== '') { log_info('Syncing user email:', email); - await Meteor.users.updateAsync({ + Meteor.users.update({ _id: user._id }, { $set: { @@ -271,7 +270,7 @@ export async function syncUserData(user, ldapUser) { } -export async function addLdapUser(ldapUser, username, password) { +export function addLdapUser(ldapUser, username, password) { const uniqueId = getLdapUserUniqueID(ldapUser); const userObject = { @@ -308,10 +307,10 @@ export async function addLdapUser(ldapUser, username, password) { try { // This creates the account with password service userObject.ldap = true; - userObject._id = await Accounts.createUserAsync(userObject); + userObject._id = Accounts.createUser(userObject); // Add the services.ldap identifiers - await Meteor.users.updateAsync({ _id: userObject._id }, { + Meteor.users.update({ _id: userObject._id }, { $set: { 'services.ldap': { id: uniqueId.value }, 'emails.0.verified': true, @@ -322,14 +321,14 @@ export async function addLdapUser(ldapUser, username, password) { return error; } - await syncUserData(userObject, ldapUser); + syncUserData(userObject, ldapUser); return { userId: userObject._id, }; } -export async function importNewUsers(ldap) { +export function importNewUsers(ldap) { if (LDAP.settings_get('LDAP_ENABLE') !== true) { log_error('Can\'t run LDAP Import, LDAP is disabled'); return; @@ -337,57 +336,65 @@ export async function importNewUsers(ldap) { if (!ldap) { ldap = new LDAP(); - await ldap.connect(); + ldap.connectSync(); } let count = 0; - const ldapUsers = await ldap.searchUsers('*'); - - for (const ldapUser of ldapUsers) { - count++; - - const uniqueId = getLdapUserUniqueID(ldapUser); - // Look to see if user already exists - const userQuery = { - 'services.ldap.id': uniqueId.value, - }; - - log_debug('userQuery', userQuery); - - let username; - if (LDAP.settings_get('LDAP_USERNAME_FIELD') !== '') { - username = slug(getLdapUsername(ldapUser)); + ldap.searchUsersSync('*', Meteor.bindEnvironment((error, ldapUsers, {next, end} = {}) => { + if (error) { + throw error; } - // Add user if it was not added before - let user = await Meteor.users.findOneAsync(userQuery); + ldapUsers.forEach((ldapUser) => { + count++; - if (!user && username && LDAP.settings_get('LDAP_MERGE_EXISTING_USERS') === true) { + const uniqueId = getLdapUserUniqueID(ldapUser); + // Look to see if user already exists const userQuery = { - username, + 'services.ldap.id': uniqueId.value, }; - log_debug('userQuery merge', userQuery); + log_debug('userQuery', userQuery); - user = await Meteor.users.findOneAsync(userQuery); - if (user) { - await syncUserData(user, ldapUser); + let username; + if (LDAP.settings_get('LDAP_USERNAME_FIELD') !== '') { + username = slug(getLdapUsername(ldapUser)); } + + // Add user if it was not added before + let user = Meteor.users.findOne(userQuery); + + if (!user && username && LDAP.settings_get('LDAP_MERGE_EXISTING_USERS') === true) { + const userQuery = { + username, + }; + + log_debug('userQuery merge', userQuery); + + user = Meteor.users.findOne(userQuery); + if (user) { + syncUserData(user, ldapUser); + } + } + + if (!user) { + addLdapUser(ldapUser, username); + } + + if (count % 100 === 0) { + log_info('Import running. Users imported until now:', count); + } + }); + + if (end) { + log_info('Import finished. Users imported:', count); } - if (!user) { - await addLdapUser(ldapUser, username); - } - - if (count % 100 === 0) { - log_info('Import running. Users imported until now:', count); - } - } - - log_info('Import finished. Users imported:', count); + next(count); + })); } -async function sync() { +function sync() { if (LDAP.settings_get('LDAP_ENABLE') !== true) { return; } @@ -395,7 +402,7 @@ async function sync() { const ldap = new LDAP(); try { - await ldap.connect(); + ldap.connectSync(); let users; if (LDAP.settings_get('LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED') === true) { @@ -403,25 +410,25 @@ async function sync() { } if (LDAP.settings_get('LDAP_BACKGROUND_SYNC_IMPORT_NEW_USERS') === true) { - await importNewUsers(ldap); + importNewUsers(ldap); } if (LDAP.settings_get('LDAP_BACKGROUND_SYNC_KEEP_EXISTANT_USERS_UPDATED') === true) { - for await (const user of users) { + users.forEach(function(user) { let ldapUser; if (user.services && user.services.ldap && user.services.ldap.id) { - ldapUser = await ldap.getUserById(user.services.ldap.id, user.services.ldap.idAttribute); + ldapUser = ldap.getUserByIdSync(user.services.ldap.id, user.services.ldap.idAttribute); } else { - ldapUser = await ldap.getUserByUsername(user.username); + ldapUser = ldap.getUserByUsernameSync(user.username); } if (ldapUser) { - await syncUserData(user, ldapUser); + syncUserData(user, ldapUser); } else { log_info('Can\'t sync user', user.username); } - } + }); } } catch (error) { log_error(error); @@ -433,7 +440,7 @@ async function sync() { const jobName = 'LDAP_Sync'; const addCronJob = _.debounce(Meteor.bindEnvironment(function addCronJobDebounced() { - let sc = SyncedCron; + let sc=SyncedCron.SyncedCron; //Why ?? something must be wrong in the import if (LDAP.settings_get('LDAP_BACKGROUND_SYNC') !== true) { log_info('Disabling LDAP Background Sync'); if (sc.nextScheduledAtDate(jobName)) { @@ -452,8 +459,8 @@ const addCronJob = _.debounce(Meteor.bindEnvironment(function addCronJobDebounce else { return parser.recur().on(0).minute(); }}, - job: async function() { - await sync(); + job: function() { + sync(); }, }); sc.start(); diff --git a/packages/wekan-ldap/server/syncUser.js b/packages/wekan-ldap/server/syncUser.js index 9558bdee1..763ea836d 100644 --- a/packages/wekan-ldap/server/syncUser.js +++ b/packages/wekan-ldap/server/syncUser.js @@ -2,15 +2,16 @@ import {importNewUsers} from './sync'; import LDAP from './ldap'; Meteor.methods({ - async ldap_sync_now() { + ldap_sync_now() { const user = Meteor.user(); if (!user) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'ldap_sync_users' }); } - if (!user.isAdmin) { - throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'ldap_sync_users' }); - } + //TODO: This needs to be fixed - security issue -> alanning:meteor-roles + //if (!RocketChat.authz.hasRole(user._id, 'admin')) { + // throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'ldap_sync_users' }); + //} if (LDAP.settings_get('LDAP_ENABLE') !== true) { throw new Meteor.Error('LDAP_disabled'); @@ -18,7 +19,7 @@ Meteor.methods({ this.unblock(); - await importNewUsers(); + importNewUsers(); return { message: 'Sync_in_progress', diff --git a/packages/wekan-ldap/server/testConnection.js b/packages/wekan-ldap/server/testConnection.js index 50ad2638a..02866ce54 100644 --- a/packages/wekan-ldap/server/testConnection.js +++ b/packages/wekan-ldap/server/testConnection.js @@ -1,7 +1,7 @@ import LDAP from './ldap'; Meteor.methods({ - async ldap_test_connection() { + ldap_test_connection() { const user = Meteor.user(); if (!user) { throw new Meteor.Error('error-invalid-user', 'Invalid user', { method: 'ldap_test_connection' }); @@ -12,21 +12,21 @@ Meteor.methods({ // throw new Meteor.Error('error-not-authorized', 'Not authorized', { method: 'ldap_test_connection' }); //} - if (LDAP.settings_get('LDAP_ENABLE') !== true) { + if (LDAP.settings_get(LDAP_ENABLE) !== true) { throw new Meteor.Error('LDAP_disabled'); } let ldap; try { ldap = new LDAP(); - await ldap.connect(); + ldap.connectSync(); } catch (error) { console.log(error); throw new Meteor.Error(error.message); } try { - await ldap.bindIfNecessary(); + ldap.bindIfNecessary(); } catch (error) { throw new Meteor.Error(error.name || error.message); } diff --git a/packages/wekan-oidc/loginHandler.js b/packages/wekan-oidc/loginHandler.js index 916c4681c..3063d0bc9 100644 --- a/packages/wekan-oidc/loginHandler.js +++ b/packages/wekan-oidc/loginHandler.js @@ -1,11 +1,11 @@ // creates Object if not present in collection // initArr = [displayName, shortName, website, isActive] // objString = ["Org","Team"] for method mapping -async function createObject(initArr, objString) +function createObject(initArr, objString) { functionName = objString === "Org" ? 'setCreateOrgFromOidc' : 'setCreateTeamFromOidc'; creationString = 'setCreate'+ objString + 'FromOidc'; - return await Meteor.callAsync(functionName, + return Meteor.call(functionName, initArr[0],//displayName initArr[1],//desc initArr[2],//shortName @@ -13,10 +13,10 @@ async function createObject(initArr, objString) initArr[4]//xxxisActive ); } -async function updateObject(initArr, objString) +function updateObject(initArr, objString) { functionName = objString === "Org" ? 'setOrgAllFieldsFromOidc' : 'setTeamAllFieldsFromOidc'; - return await Meteor.callAsync(functionName, + return Meteor.call(functionName, initArr[0],//team || org Object initArr[1],//displayName initArr[2],//desc @@ -57,7 +57,7 @@ module.exports = { // isAdmin: [true, false] -> admin group becomes admin in wekan // isOrganization: [true, false] -> creates org and adds to user // displayName: "string" -addGroupsWithAttributes: async function (user, groups){ +addGroupsWithAttributes: function (user, groups){ teamArray=[]; orgArray=[]; isAdmin = []; @@ -76,20 +76,20 @@ addGroupsWithAttributes: async function (user, groups){ isAdmin.push(group.isAdmin || false); if (isOrg) { - org = await Org.findOneAsync({"orgDisplayName": group.displayName}); + org = Org.findOne({"orgDisplayName": group.displayName}); if(org) { if(contains(orgs, org, "org")) { initAttributes.unshift(org); - await updateObject(initAttributes, "Org"); + updateObject(initAttributes, "Org"); continue; } } else if(forceCreate) { - await createObject(initAttributes, "Org"); - org = await Org.findOneAsync({'orgDisplayName': group.displayName}); + createObject(initAttributes, "Org"); + org = Org.findOne({'orgDisplayName': group.displayName}); } else { @@ -102,20 +102,20 @@ addGroupsWithAttributes: async function (user, groups){ else { //start team routine - team = await Team.findOneAsync({"teamDisplayName": group.displayName}); + team = Team.findOne({"teamDisplayName": group.displayName}); if (team) { if(contains(teams, team, "team")) { initAttributes.unshift(team); - await updateObject(initAttributes, "Team"); + updateObject(initAttributes, "Team"); continue; } } else if(forceCreate) { - await createObject(initAttributes, "Team"); - team = await Team.findOneAsync({'teamDisplayName': group.displayName}); + createObject(initAttributes, "Team"); + team = Team.findOne({'teamDisplayName': group.displayName}); } else { @@ -129,28 +129,28 @@ addGroupsWithAttributes: async function (user, groups){ // hence user will get admin privileges in wekan // E.g. Admin rights will be withdrawn if no group in oidc provider has isAdmin set to true - await users.updateAsync({ _id: user._id }, { $set: {isAdmin: isAdmin.some(i => (i === true))}}); + users.update({ _id: user._id }, { $set: {isAdmin: isAdmin.some(i => (i === true))}}); teams = {'teams': {'$each': teamArray}}; orgs = {'orgs': {'$each': orgArray}}; - await users.updateAsync({ _id: user._id }, { $push: teams}); - await users.updateAsync({ _id: user._id }, { $push: orgs}); + users.update({ _id: user._id }, { $push: teams}); + users.update({ _id: user._id }, { $push: orgs}); // remove temporary oidc data from user collection - await users.updateAsync({ _id: user._id }, { $unset: {"services.oidc.groups": []}}); + users.update({ _id: user._id }, { $unset: {"services.oidc.groups": []}}); return; }, -changeUsername: async function(user, name) +changeUsername: function(user, name) { username = {'username': name}; - if (user.username != username) await users.updateAsync({ _id: user._id }, { $set: username}); + if (user.username != username) users.update({ _id: user._id }, { $set: username}); }, -changeFullname: async function(user, name) +changeFullname: function(user, name) { username = {'profile.fullname': name}; - if (user.username != username) await users.updateAsync({ _id: user._id }, { $set: username}); + if (user.username != username) users.update({ _id: user._id }, { $set: username}); }, -addEmail: async function(user, email) +addEmail: function(user, email) { user_email = user.emails || []; var contained = false; @@ -173,7 +173,7 @@ addEmail: async function(user, email) { user_email.unshift({'address': email, 'verified': true}); user_email = {'emails': user_email}; - await users.updateAsync({ _id: user._id }, { $set: user_email}); + users.update({ _id: user._id }, { $set: user_email}); } } } diff --git a/packages/wekan-oidc/oidc_server.js b/packages/wekan-oidc/oidc_server.js index 86ecb265d..04a304290 100644 --- a/packages/wekan-oidc/oidc_server.js +++ b/packages/wekan-oidc/oidc_server.js @@ -1,15 +1,11 @@ import {addGroupsWithAttributes, addEmail, changeFullname, changeUsername} from './loginHandler'; -import { fetch, Headers } from 'meteor/fetch'; -import { URLSearchParams } from 'meteor/url'; -import { Buffer } from 'node:buffer'; -import https from 'https'; -import fs from 'fs'; Oidc = {}; httpCa = false; if (process.env.OAUTH2_CA_CERT !== undefined) { try { + const fs = Npm.require('fs'); if (fs.existsSync(process.env.OAUTH2_CA_CERT)) { httpCa = fs.readFileSync(process.env.OAUTH2_CA_CERT); } @@ -22,10 +18,10 @@ var profile = {}; var serviceData = {}; var userinfo = {}; -OAuth.registerService('oidc', 2, null, async function (query) { +OAuth.registerService('oidc', 2, null, function (query) { var debug = process.env.DEBUG === 'true'; - var token = await getToken(query); + var token = getToken(query); if (debug) console.log('XXX: register token:', token); var accessToken = token.access_token || token.id_token; @@ -44,7 +40,7 @@ OAuth.registerService('oidc', 2, null, async function (query) { else { // normal behaviour, getting the claims from UserInfo endpoint. - userinfo = await getUserInfo(accessToken); + userinfo = getUserInfo(accessToken); } if (userinfo.ocs) userinfo = userinfo.ocs.data; // Nextcloud hack @@ -77,8 +73,7 @@ OAuth.registerService('oidc', 2, null, async function (query) { if (accessToken) { var tokenContent = getTokenContent(accessToken); - var config = await getConfiguration(); - var fields = _.pick(tokenContent, config.idTokenWhitelistFields); + var fields = _.pick(tokenContent, getConfiguration().idTokenWhitelistFields); _.extend(serviceData, fields); } @@ -105,7 +100,7 @@ OAuth.registerService('oidc', 2, null, async function (query) { // therefore: keep admin privileges for wekan as before if(Array.isArray(serviceData.groups) && serviceData.groups.length && typeof serviceData.groups[0] === "string" ) { - user = await Meteor.users.findOneAsync({'_id': serviceData.id}); + user = Meteor.users.findOne({'_id': serviceData.id}); serviceData.groups.forEach(function(groupName, i) { @@ -124,8 +119,8 @@ OAuth.registerService('oidc', 2, null, async function (query) { // Fix OIDC login loop for integer user ID. Thanks to danielkaiser. // https://github.com/wekan/wekan/issues/4795 - await Meteor.callAsync('groupRoutineOnLogin',serviceData, ""+serviceData.id); - await Meteor.callAsync('boardRoutineOnLogin',serviceData, ""+serviceData.id); + Meteor.call('groupRoutineOnLogin',serviceData, ""+serviceData.id); + Meteor.call('boardRoutineOnLogin',serviceData, ""+serviceData.id); return { serviceData: serviceData, @@ -139,166 +134,143 @@ if (Meteor.release) { } if (process.env.ORACLE_OIM_ENABLED !== 'true' && process.env.ORACLE_OIM_ENABLED !== true) { - var getToken = async function (query) { + var getToken = function (query) { var debug = process.env.DEBUG === 'true'; - var config = await getConfiguration(); - var serverTokenEndpoint; + var config = getConfiguration(); if(config.tokenEndpoint.includes('https://')){ - serverTokenEndpoint = config.tokenEndpoint; + var serverTokenEndpoint = config.tokenEndpoint; }else{ - serverTokenEndpoint = config.serverUrl + config.tokenEndpoint; + var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint; } + var requestPermissions = config.requestPermissions; + var response; try { - var body = new URLSearchParams({ - code: query.code, - client_id: config.clientId, - client_secret: OAuth.openSecret(config.secret), - redirect_uri: OAuth._redirectUri('oidc', config), - grant_type: 'authorization_code', - state: query.state - }); - - var fetchOptions = { - method: 'POST', - headers: new Headers({ - 'Accept': 'application/json', - 'User-Agent': userAgent, - 'Content-Type': 'application/x-www-form-urlencoded' - }), - body: body.toString() - }; - + var postOptions = { + headers: { + Accept: 'application/json', + "User-Agent": userAgent + }, + params: { + code: query.code, + client_id: config.clientId, + client_secret: OAuth.openSecret(config.secret), + redirect_uri: OAuth._redirectUri('oidc', config), + grant_type: 'authorization_code', + state: query.state + } + }; if (httpCa) { - fetchOptions.agent = new https.Agent({ ca: httpCa }); + postOptions['npmRequestOptions'] = { ca: httpCa }; } - - var response = await fetch(serverTokenEndpoint, fetchOptions); - var data = await response.json(); - - if (!response.ok) { - throw new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + response.statusText); - } - if (data.error) { - // if the http response was a json object with an error attribute - throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + data.error); - } - if (debug) console.log('XXX: getToken response: ', data); - return data; + response = HTTP.post(serverTokenEndpoint, postOptions); } catch (err) { throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message), { response: err.response }); } + if (response.data.error) { + // if the http response was a json object with an error attribute + throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + response.data.error); + } else { + if (debug) console.log('XXX: getToken response: ', response.data); + return response.data; + } }; } if (process.env.ORACLE_OIM_ENABLED === 'true' || process.env.ORACLE_OIM_ENABLED === true) { - var getToken = async function (query) { + var getToken = function (query) { var debug = process.env.DEBUG === 'true'; - var config = await getConfiguration(); - var serverTokenEndpoint; + var config = getConfiguration(); if(config.tokenEndpoint.includes('https://')){ - serverTokenEndpoint = config.tokenEndpoint; + var serverTokenEndpoint = config.tokenEndpoint; }else{ - serverTokenEndpoint = config.serverUrl + config.tokenEndpoint; + var serverTokenEndpoint = config.serverUrl + config.tokenEndpoint; } + var requestPermissions = config.requestPermissions; + var response; // OIM needs basic Authentication token in the header - ClientID + SECRET in base64 - var dataToken = process.env.OAUTH2_CLIENT_ID + ':' + process.env.OAUTH2_SECRET; - var strBasicToken64 = Buffer.from(dataToken).toString('base64'); + var dataToken=null; + var strBasicToken=null; + var strBasicToken64=null; + + dataToken = process.env.OAUTH2_CLIENT_ID + ':' + process.env.OAUTH2_SECRET; + strBasicToken = new Buffer(dataToken); + strBasicToken64 = strBasicToken.toString('base64'); // eslint-disable-next-line no-console if (debug) console.log('Basic Token: ', strBasicToken64); try { - var body = new URLSearchParams({ - code: query.code, - client_id: config.clientId, - client_secret: OAuth.openSecret(config.secret), - redirect_uri: OAuth._redirectUri('oidc', config), - grant_type: 'authorization_code', - state: query.state - }); - - var fetchOptions = { - method: 'POST', - headers: new Headers({ - 'Accept': 'application/json', - 'User-Agent': userAgent, - 'Content-Type': 'application/x-www-form-urlencoded', - 'Authorization': 'Basic ' + strBasicToken64 - }), - body: body.toString() - }; - + var postOptions = { + headers: { + Accept: 'application/json', + "User-Agent": userAgent, + "Authorization": "Basic " + strBasicToken64 + }, + params: { + code: query.code, + client_id: config.clientId, + client_secret: OAuth.openSecret(config.secret), + redirect_uri: OAuth._redirectUri('oidc', config), + grant_type: 'authorization_code', + state: query.state + } + }; if (httpCa) { - fetchOptions.agent = new https.Agent({ ca: httpCa }); + postOptions['npmRequestOptions'] = { ca: httpCa }; } - - var response = await fetch(serverTokenEndpoint, fetchOptions); - var data = await response.json(); - - if (!response.ok) { - throw new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + response.statusText); - } - if (data.error) { - // if the http response was a json object with an error attribute - throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + data.error); - } - // eslint-disable-next-line no-console - if (debug) console.log('XXX: getToken response: ', data); - return data; + response = HTTP.post(serverTokenEndpoint, postOptions); } catch (err) { throw _.extend(new Error("Failed to get token from OIDC " + serverTokenEndpoint + ": " + err.message), { response: err.response }); } + if (response.data.error) { + // if the http response was a json object with an error attribute + throw new Error("Failed to complete handshake with OIDC " + serverTokenEndpoint + ": " + response.data.error); + } else { + // eslint-disable-next-line no-console + if (debug) console.log('XXX: getToken response: ', response.data); + return response.data; + } }; } -var getUserInfo = async function (accessToken) { +var getUserInfo = function (accessToken) { var debug = process.env.DEBUG === 'true'; - var config = await getConfiguration(); + var config = getConfiguration(); // Some userinfo endpoints use a different base URL than the authorization or token endpoints. // This logic allows the end user to override the setting by providing the full URL to userinfo in their config. - var serverUserinfoEndpoint; if (config.userinfoEndpoint.includes("https://")) { - serverUserinfoEndpoint = config.userinfoEndpoint; + var serverUserinfoEndpoint = config.userinfoEndpoint; } else { - serverUserinfoEndpoint = config.serverUrl + config.userinfoEndpoint; + var serverUserinfoEndpoint = config.serverUrl + config.userinfoEndpoint; } - + var response; try { - var fetchOptions = { - method: 'GET', - headers: new Headers({ - 'User-Agent': userAgent, - 'Authorization': 'Bearer ' + accessToken - }) - }; - + var getOptions = { + headers: { + "User-Agent": userAgent, + "Authorization": "Bearer " + accessToken + } + }; if (httpCa) { - fetchOptions.agent = new https.Agent({ ca: httpCa }); + getOptions['npmRequestOptions'] = { ca: httpCa }; } - - var response = await fetch(serverUserinfoEndpoint, fetchOptions); - - if (!response.ok) { - throw new Error("Failed to fetch userinfo from OIDC " + serverUserinfoEndpoint + ": " + response.statusText); - } - - var data = await response.json(); - if (debug) console.log('XXX: getUserInfo response: ', data); - return data; + response = HTTP.get(serverUserinfoEndpoint, getOptions); } catch (err) { throw _.extend(new Error("Failed to fetch userinfo from OIDC " + serverUserinfoEndpoint + ": " + err.message), {response: err.response}); } + if (debug) console.log('XXX: getUserInfo response: ', response.data); + return response.data; }; -var getConfiguration = async function () { - var config = await ServiceConfiguration.configurations.findOneAsync({ service: 'oidc' }); +var getConfiguration = function () { + var config = ServiceConfiguration.configurations.findOne({ service: 'oidc' }); if (!config) { throw new ServiceConfiguration.ConfigError('Service oidc not configured.'); } @@ -323,31 +295,31 @@ var getTokenContent = function (token) { return content; } Meteor.methods({ - 'groupRoutineOnLogin': async function(info, userId) + 'groupRoutineOnLogin': function(info, userId) { check(info, Object); check(userId, String); var propagateOidcData = process.env.PROPAGATE_OIDC_DATA || false; if (propagateOidcData) { users= Meteor.users; - user = await users.findOneAsync({'services.oidc.id': userId}); + user = users.findOne({'services.oidc.id': userId}); if(user) { //updates/creates Groups and user admin privileges accordingly if not undefined if (info.groups) { - await addGroupsWithAttributes(user, info.groups); + addGroupsWithAttributes(user, info.groups); } - if(info.email) await addEmail(user, info.email); - if(info.fullname) await changeFullname(user, info.fullname); - if(info.username) await changeUsername(user, info.username); + if(info.email) addEmail(user, info.email); + if(info.fullname) changeFullname(user, info.fullname); + if(info.username) changeUsername(user, info.username); } } } }); Meteor.methods({ - 'boardRoutineOnLogin': async function(info, oidcUserId) + 'boardRoutineOnLogin': function(info, oidcUserId) { check(info, Object); check(oidcUserId, String); @@ -356,14 +328,13 @@ Meteor.methods({ const defaultBoardId = defaultBoardParams.shift() if (!defaultBoardId) return - const board = await Boards.findOneAsync(defaultBoardId) - const user = await Users.findOneAsync({ 'services.oidc.id': oidcUserId }) - const userId = user?._id + const board = Boards.findOne(defaultBoardId) + const userId = Users.findOne({ 'services.oidc.id': oidcUserId })?._id const memberIndex = _.pluck(board?.members, 'userId').indexOf(userId); if(!board || !userId || memberIndex > -1) return - await board.addMember(userId) - await board.setMemberPermission( + board.addMember(userId) + board.setMemberPermission( userId, defaultBoardParams.contains("isAdmin"), defaultBoardParams.contains("isNoComments"), diff --git a/packages/wekan-oidc/package.js b/packages/wekan-oidc/package.js index 83d68bde7..fee31e566 100644 --- a/packages/wekan-oidc/package.js +++ b/packages/wekan-oidc/package.js @@ -1,6 +1,6 @@ Package.describe({ summary: "OpenID Connect (OIDC) flow for Meteor", - version: "1.1.0", + version: "1.0.12", name: "wekan-oidc", git: "https://github.com/wekan/wekan-oidc.git", }); @@ -8,7 +8,7 @@ Package.describe({ Package.onUse(function(api) { api.use('oauth2', ['client', 'server']); api.use('oauth', ['client', 'server']); - api.use('fetch', ['server']); + api.use('http', ['server']); api.use('underscore', 'client'); api.use('ecmascript'); api.use('templating', 'client'); diff --git a/popup.jade b/popup.jade deleted file mode 100644 index 92433a1cd..000000000 --- a/popup.jade +++ /dev/null @@ -1,21 +0,0 @@ -template(name="popupPlaceholder") - span(class=popupPlaceholderClass) - -template(name="popupDetached") - .pop-over.js-pop-over( - class="{{#unless title}}miniprofile{{/unless}}" - class=currentBoard.colorClass - class="{{#unless title}}no-title{{/unless}}" - class="{{#unless isRendered}}invisible{{/unless}}" - data-popup=name) - if showHeader - .header - span.header-title= title - .header-controls - if isMiniScreen - span.popup-drag-handle.js-popup-drag-handle(title="Drag popup") - i.fa.fa-arrows - a.close-btn.js-close-detached-popup - i.fa.fa-times-thin - .content-wrapper - .content diff --git a/public/.well-known/assetlinks.json.default b/public/.well-known/assetlinks.json similarity index 100% rename from public/.well-known/assetlinks.json.default rename to public/.well-known/assetlinks.json diff --git a/public/api/wekan.yml b/public/api/wekan.yml index b2dd253fd..cbb1a23a6 100644 --- a/public/api/wekan.yml +++ b/public/api/wekan.yml @@ -1115,6 +1115,12 @@ paths: - multipart/form-data - application/json parameters: + - name: authorId + in: formData + description: | + the user who 'posted' the comment + type: string + required: true - name: comment in: formData description: the comment value @@ -2588,53 +2594,6 @@ paths: properties: _id: type: string - /api/boards/{board}/lists/{list}/cards/{card}/archive: - post: - operationId: archive_card - summary: Archive a card - description: | - Archive a card - tags: - - Cards - consumes: - - multipart/form-data - - application/json - parameters: - - name: board - in: path - description: | - the board ID of the card - type: string - required: true - - name: list - in: path - description: | - the list ID of the card - type: string - required: true - - name: card - in: path - description: | - the ID of the card - type: string - required: true - produces: - - application/json - security: - - UserSecurity: [] - responses: - '200': - description: |- - 200 response - schema: - type: object - properties: - _id: - type: string - archived: - type: boolean - archivedAt: - type: string /api/boards/{board}/lists/{list}/cards/{card}/customFields/{customField}: post: operationId: edit_card_custom_field @@ -2699,51 +2658,6 @@ paths: type: string value: type: object - /api/boards/{board}/lists/{list}/cards/{card}/unarchive: - post: - operationId: unarchive_card - summary: Unarchive card - description: | - Unarchive card - tags: - - Cards - consumes: - - multipart/form-data - - application/json - parameters: - - name: board - in: path - description: | - the board ID of the card - type: string - required: true - - name: list - in: path - description: | - the list ID of the card - type: string - required: true - - name: card - in: path - description: | - the ID of the card - type: string - required: true - produces: - - application/json - security: - - UserSecurity: [] - responses: - '200': - description: |- - 200 response - schema: - type: object - properties: - _id: - type: string - archived: - type: boolean /api/boards/{board}/lists/{list}/cards_count: get: operationId: get_list_cards_count diff --git a/public/css/reset.css b/public/css/reset.css index a152f5e4f..3839cb35c 100644 --- a/public/css/reset.css +++ b/public/css/reset.css @@ -46,9 +46,14 @@ article, aside, canvas, details, figcaption, display: block; } audio, canvas, video { - display: inline-block; - zoom: 1; + display inline-block; + *display inline; + *zoom 1; } audio:not([controls]),[hidden] { display: none; } + + + + diff --git a/public/site.webmanifest.default b/public/site.webmanifest similarity index 100% rename from public/site.webmanifest.default rename to public/site.webmanifest diff --git a/rebuild-wekan.sh b/rebuild-wekan.sh index f27c2d0f6..45067cfa0 100755 --- a/rebuild-wekan.sh +++ b/rebuild-wekan.sh @@ -12,7 +12,7 @@ function pause(){ echo PS3='Please enter your choice: ' -options=("Install Wekan dependencies" "Build Wekan" "Run Meteor for dev on http://localhost:4000" "Run Meteor for dev on http://localhost:4000 with trace warnings, and warnings using old Meteor API that will not exist in Meteor 3.0" "Run Meteor for dev on http://localhost:4000 with bundle visualizer" "Run Meteor for dev on http://CURRENT-IP-ADDRESS:4000" "Run Meteor for dev on http://CURRENT-IP-ADDRESS:4000 with MONGO_URL=mongodb://127.0.0.1:27019/wekan" "Run Meteor for dev on http://CUSTOM-IP-ADDRESS:PORT" "Run tests" "Save Meteor dependency chain to ../meteor-deps.txt" "Quit") +options=("Install Wekan dependencies" "Build Wekan" "Run Meteor for dev on http://localhost:4000" "Run Meteor for dev on http://localhost:4000 with trace warnings, and warnings using old Meteor API that will not exist in Meteor 3.0" "Run Meteor for dev on http://localhost:4000 with bundle visualizer" "Run Meteor for dev on http://CURRENT-IP-ADDRESS:4000" "Run Meteor for dev on http://CURRENT-IP-ADDRESS:4000 with MONGO_URL=mongodb://127.0.0.1:27019/wekan" "Run Meteor for dev on http://CUSTOM-IP-ADDRESS:PORT" "Save Meteor dependency chain to ../meteor-deps.txt" "Quit") select opt in "${options[@]}" do @@ -47,7 +47,7 @@ do # Latest fibers for Meteor sudo mkdir -p /usr/local/lib/node_modules/fibers/.node-gyp sudo npm -g install fibers sudo npm -g install @mapbox/node-pre-gyp # Install Meteor, if it's not yet installed - sudo npm -g install meteor@2.16 --unsafe-perm + sudo npm -g install meteor@2.14 --unsafe-perm #sudo chown -R $(id -u):$(id -g) $HOME/.npm $HOME/.meteor elif [[ "$OSTYPE" == "darwin"* ]]; then echo "macOS" @@ -89,7 +89,7 @@ do npm -g uninstall node-pre-gyp npm -g install @mapbox/node-pre-gyp npm -g install node-gyp - npm -g install meteor@2.16 + npm -g install meteor@2.14 export PATH=~/.meteor:$PATH exit; elif [[ "$OSTYPE" == "cygwin" ]]; then @@ -240,12 +240,6 @@ do break ;; - "Run tests") - echo "Running tests (import regression)." - node tests/wekanCreator.import.test.js - break - ;; - "Quit") break ;; diff --git a/releases/build-bundle-arm64.sh b/releases/build-bundle-arm64.sh index af561362e..53687545a 100755 --- a/releases/build-bundle-arm64.sh +++ b/releases/build-bundle-arm64.sh @@ -10,40 +10,22 @@ if [ $# -ne 1 ] exit 1 fi -# Install deps sudo apt -y install g++ build-essential p7zip-full sudo npm -g uninstall node-pre-gyp sudo npm -g install @mapbox/node-pre-gyp -# Remove old files -rm -rf bundle 7.93 -rm wekan-$1-arm64.zip wekan-7.93-arm64.zip - -# Download newest WeKan, and WeKan v7.93 that has working fibers and bcrypt -wget --no-check-certificate https://github.com/wekan/wekan/releases/download/v$1/wekan-$1-amd64.zip -wget --no-check-certificate https://github.com/wekan/wekan/releases/download/v7.93/wekan-7.93-arm64.zip - -# Unarchive newest WeKan and WeKan v7.93 +rm -rf bundle +rm wekan-$1-arm64.zip +#rm wekan-$1.zip +#wget https://releases.wekan.team/wekan-$1.zip 7z x wekan-$1-amd64.zip -(mkdir 7.93 && cd 7.93 && 7z x ../wekan-7.93-arm64.zip) -# Add working bcrypt -rm -rf ~/repos/wekan/bundle/programs/server/npm/node_modules/meteor/accounts-password/node_modules/bcrypt -cp -pR ~/repos/wekan/7.93/bundle/programs/server/npm/node_modules/meteor/accounts-password/node_modules/bcrypt \ -~/repos/wekan/bundle/programs/server/npm/node_modules/meteor/accounts-password/node_modules/ - -# Add working fibers -rm -rf ~/repos/wekan/bundle/programs/server/node_modules/fibers -cp -pR ~/repos/wekan/7.93/bundle/programs/server/node_modules/fibers \ -~/repos/wekan/bundle/programs/server/node_modules/ - -##(cd bundle/programs/server && chmod u+w *.json && cd node_modules/fibers && node build.js) +(cd bundle/programs/server && chmod u+w *.json && cd node_modules/fibers && node build.js) #cd ../../../.. #(cd bundle/programs/server/npm/node_modules/meteor/accounts-password && npm remove bcrypt && npm install bcrypt) # Requires building from source https://github.com/meteor/meteor/issues/11682 -##(cd bundle/programs/server/npm/node_modules/meteor/accounts-password && npm rebuild --build-from-source) +(cd bundle/programs/server/npm/node_modules/meteor/accounts-password && npm rebuild --build-from-source) -# Remove temporary files cd bundle find . -type d -name '*-garbage*' | xargs rm -rf find . -name '*phantom*' | xargs rm -rf @@ -51,9 +33,8 @@ find . -name '.*.swp' | xargs rm -f find . -name '*.swp' | xargs rm -f cd .. -# Make newest WeKan bundle for Linux arm64 7z a wekan-$1-arm64.zip bundle -#sudo snap start juju-db +sudo snap start juju-db -#./start-wekan.sh +./start-wekan.sh diff --git a/releases/build-bundle-s390x.sh b/releases/build-bundle-s390x.sh index 63dae8add..da2e390c7 100755 --- a/releases/build-bundle-s390x.sh +++ b/releases/build-bundle-s390x.sh @@ -25,42 +25,19 @@ fi # N_PREFIX="$HOME/.local/bin" # export N_PREFIX # -#rm -rf bundle -#rm wekan-$1-s390x.zip -#7za x wekan-$1-amd64.zip +rm -rf bundle +rm wekan-$1-s390x.zip -# Install deps -sudo apt -y install g++ build-essential p7zip-full -sudo npm -g uninstall node-pre-gyp -sudo npm -g install @mapbox/node-pre-gyp -# Remove old files -rm -rf bundle 7.93 -rm wekan-$1-s390x.zip wekan-7.93-s390x.zip +#rm wekan-$1.zip +#wget https://releases.wekan.team/wekan-$1-amd64.zip +7za x wekan-$1-amd64.zip -# Download newest WeKan, and WeKan v7.93 that has working fibers and bcrypt -wget --no-check-certificate https://github.com/wekan/wekan/releases/download/v$1/wekan-$1-amd64.zip -wget --no-check-certificate https://github.com/wekan/wekan/releases/download/v7.93/wekan-7.93-s390x.zip - -# Unarchive newest WeKan and WeKan v7.93 -7z x wekan-$1-amd64.zip -(mkdir 7.93 && cd 7.93 && 7z x ../wekan-7.93-s390x.zip) - -# Add working bcrypt -#rm -rf ~/repos/wekan/bundle/programs/server/npm/node_modules/meteor/accounts-password/node_modules/bcrypt -#cp -pR ~/repos/wekan/7.93/bundle/programs/server/npm/node_modules/meteor/accounts-password/node_modules/bcrypt \ -#~/repos/wekan/bundle/programs/server/npm/node_modules/meteor/accounts-password/node_modules/ - -# Add working fibers -rm -rf ~/repos/wekan/bundle/programs/server/node_modules/fibers -cp -pR ~/repos/wekan/7.93/bundle/programs/server/node_modules/fibers \ -~/repos/wekan/bundle/programs/server/node_modules/ - -#(cd bundle/programs/server && chmod u+w *.json && cd node_modules/fibers && node build.js) +(cd bundle/programs/server && chmod u+w *.json && cd node_modules/fibers && node build.js) #cd ../../../.. -(cd bundle/programs/server/npm/node_modules/meteor/accounts-password && npm remove bcrypt && npm install bcrypt) +#(cd bundle/programs/server/npm/node_modules/meteor/accounts-password && npm remove bcrypt && npm install bcrypt) # Requires building from source https://github.com/meteor/meteor/issues/11682 -#(cd bundle/programs/server/npm/node_modules/meteor/accounts-password && npm rebuild --build-from-source) +(cd bundle/programs/server/npm/node_modules/meteor/accounts-password && npm rebuild --build-from-source) cd bundle find . -type d -name '*-garbage*' | xargs rm -rf @@ -69,11 +46,8 @@ find . -name '.*.swp' | xargs rm -f find . -name '*.swp' | xargs rm -f cd .. -# Make newest WeKan bundle for Linux s390x -7z a wekan-$1-s390x.zip bundle +7za a wekan-$1-s390x.zip bundle -#7za a wekan-$1-s390x.zip bundle +sudo snap start juju-db -#sudo snap start juju-db - -#./start-wekan.sh +./start-wekan.sh diff --git a/releases/build-bundle-win64.bat b/releases/build-bundle-win64.bat index 05aa54ab5..f7e0b1f93 100755 --- a/releases/build-bundle-win64.bat +++ b/releases/build-bundle-win64.bat @@ -12,7 +12,7 @@ CALL DEL /F /S /Q bundle ECHO 2) Downloading new WeKan.zip DEL wekan-%1-amd64.zip -wget --no-check-certificate https://github.com/wekan/wekan/releases/download/v%1/wekan-%1-amd64.zip +wget https://github.com/wekan/wekan/releases/download/v%1/wekan-%1-amd64.zip ECHO 3) Unarchiving new WeKan CALL 7z x wekan-%1-amd64.zip diff --git a/releases/count-lines-of-code-per-committer.sh b/releases/count-lines-of-code-per-committer.sh index f1d807b80..0b429733f 100755 --- a/releases/count-lines-of-code-per-committer.sh +++ b/releases/count-lines-of-code-per-committer.sh @@ -11,8 +11,8 @@ if [ $# -ne 1 ] then echo "Syntax to count lines of code per committer, by email address:" echo " ./releases/count-lines-of-code-per-committer.sh x@xet7.org" - echo "Example result at 2026-01-24:" - echo " added lines: 4842862, removed lines: 4550521, total lines: 292341, added:deleted ratio:1.06424" + echo "Example result at 2024-03-08:" + echo " added lines: 4594802, removed lines: 4416066, total lines: 178736, added:deleted ratio:1.04047" exit 1 fi diff --git a/releases/docker-build-deps.sh b/releases/docker-build-deps.sh deleted file mode 100755 index a34ca52c4..000000000 --- a/releases/docker-build-deps.sh +++ /dev/null @@ -1,5 +0,0 @@ -# Create a new builder instance that supports multi-platform -docker buildx create --name mybuilder --driver docker-container --use - -# Start the builder -docker buildx inspect --bootstrap diff --git a/releases/docker-build.sh b/releases/docker-build.sh index 2c127d714..0d076b5bc 100755 --- a/releases/docker-build.sh +++ b/releases/docker-build.sh @@ -1,23 +1,10 @@ #!/bin/bash -if [ $# -ne 1 ] - then - echo "Syntax with Wekan version number:" - echo " ./releases/docker-build.sh 8.24" - exit 1 -fi +# Build Docker images locally, because builds at Quay.io and Docker Hub usually fail. +# +# To be done at ~/repos/wekan or ~/repos/w/wekan-gantt-gpl +# +# After building, you see created Docker image ID, that is then +# used with releases/docker-push-...sh scripts. -VERSION=$1 - -# Ensure you are using the correct builder -docker buildx use mybuilder - -docker buildx build \ - --platform linux/amd64,linux/arm64,linux/s390x \ - -t wekanteam/wekan:v${VERSION} \ - -t wekanteam/wekan:latest \ - -t quay.io/wekan/wekan:v${VERSION} \ - -t quay.io/wekan/wekan:latest \ - -t ghcr.io/wekan/wekan:v${VERSION} \ - -t ghcr.io/wekan/wekan:latest \ - --push . +docker build . diff --git a/releases/docker-push-wekan.sh b/releases/docker-push-wekan.sh new file mode 100755 index 000000000..8efb3dfa8 --- /dev/null +++ b/releases/docker-push-wekan.sh @@ -0,0 +1,35 @@ +#!/bin/bash + +# Push locally built docker images to Quay.io and Docker Hub. + +# Check that there is 2 parameters of +# of Wekan version number: + +if [ $# -ne 2 ] + then + echo "Usage: ./push-docker.sh DOCKERBUILDTAG WEKANVERSION" + echo "Example: ./push-docker.sh 12345 5.70" + exit 1 +fi + +#sudo apt -y install skopeo +#~/repos/wekan/releases/docker-registry-sync.sh + +# Quay +docker tag $1 quay.io/wekan/wekan:v$2 +docker push quay.io/wekan/wekan:v$2 +docker tag $1 quay.io/wekan/wekan:latest +docker push quay.io/wekan/wekan:latest + + +# Docker Hub +docker tag $1 wekanteam/wekan:v$2 +docker push wekanteam/wekan:v$2 +docker tag $1 wekanteam/wekan:latest +docker push wekanteam/wekan:latest + +# GitHub +docker tag $1 ghcr.io/wekan/wekan:v$2 +docker push ghcr.io/wekan/wekan:v$2 +docker tag $1 ghcr.io/wekan/wekan:latest +docker push ghcr.io/wekan/wekan:latest diff --git a/sandstorm-pkgdef.capnp b/sandstorm-pkgdef.capnp index a2ecf44ef..21401c3ce 100644 --- a/sandstorm-pkgdef.capnp +++ b/sandstorm-pkgdef.capnp @@ -22,10 +22,10 @@ const pkgdef :Spk.PackageDefinition = ( appTitle = (defaultText = "Wekan"), # The name of the app as it is displayed to the user. - appVersion = 835, + appVersion = 816, # Increment this for every release. - appMarketingVersion = (defaultText = "8.35.0~2026-03-05"), + appMarketingVersion = (defaultText = "8.16.0~2025-11-02"), # Human-readable presentation of the app version. minUpgradableAppVersion = 0, diff --git a/sandstorm.js b/sandstorm.js index 2d2c79b28..b50922794 100644 --- a/sandstorm.js +++ b/sandstorm.js @@ -1,7 +1,6 @@ import { ReactiveCache } from '/imports/reactiveCache'; import { Meteor } from 'meteor/meteor'; import { Picker } from 'meteor/communitypackages:picker'; -import { FlowRouter } from 'meteor/ostrio:flow-router-extra'; // Sandstorm context is detected using the METEOR_SETTINGS environment variable // in the package definition. @@ -51,7 +50,7 @@ if (isSandstorm && Meteor.isServer) { } Meteor.methods({ - async sandstormClaimIdentityRequest(token, descriptor) { + sandstormClaimIdentityRequest(token, descriptor) { check(token, String); check(descriptor, String); @@ -79,68 +78,91 @@ if (isSandstorm && Meteor.isServer) { const session = httpBridge.getSessionContext(sessionId).context; const api = httpBridge.getSandstormApi(sessionId).api; - const response = await session.claimRequest(token); - const identity = response.cap.castAs(Identity.Identity); - const [identityIdResult, profileResult] = await Promise.all([ - api.getIdentityId(identity), - identity.getProfile(), - httpBridge.saveIdentity(identity), - ]); - const identityId = identityIdResult.id.toString('hex').slice(0, 32); - const profile = profileResult.profile; - const pictureResponse = await profile.picture.getUrl(); - const sandstormInfo = { - id: identityId, - name: profile.displayName.defaultText, - permissions, - picture: `${pictureResponse.protocol}://${pictureResponse.hostPath}`, - preferredHandle: profile.preferredHandle, - pronouns: profile.pronouns, - }; + Meteor.wrapAsync(done => { + session + .claimRequest(token) + .then(response => { + const identity = response.cap.castAs(Identity.Identity); + const promises = [ + api.getIdentityId(identity), + identity.getProfile(), + httpBridge.saveIdentity(identity), + ]; + return Promise.all(promises).then(responses => { + const identityId = responses[0].id.toString('hex').slice(0, 32); + const profile = responses[1].profile; + return profile.picture.getUrl().then(response => { + const sandstormInfo = { + id: identityId, + name: profile.displayName.defaultText, + permissions, + picture: `${response.protocol}://${response.hostPath}`, + preferredHandle: profile.preferredHandle, + pronouns: profile.pronouns, + }; - const login = await Accounts.updateOrCreateUserFromExternalService( - 'sandstorm', - sandstormInfo, - { profile: { name: sandstormInfo.name } }, - ); + const login = Accounts.updateOrCreateUserFromExternalService( + 'sandstorm', + sandstormInfo, + { profile: { name: sandstormInfo.name } }, + ); - await updateUserPermissions(login.userId, permissions); + updateUserPermissions(login.userId, permissions); + done(); + }); + }); + }) + .catch(e => { + done(e, null); + }); + })(); }, }); - async function reportActivity(sessionId, path, type, users, caption) { + function reportActivity(sessionId, path, type, users, caption) { const httpBridge = getHttpBridge(); const session = httpBridge.getSessionContext(sessionId).context; - const maybeUsers = await Promise.all( - users.map(async (user) => { - try { - const response = await httpBridge.getSavedIdentity(user.id); - // Call getProfile() to make sure that the identity successfully resolves. - // (In C++ we would instead call whenResolved() here.) - const identity = response.identity; - await identity.getProfile(); - return { - identity, - mentioned: !!user.mentioned, - subscribed: !!user.subscribed, - }; - } catch (e) { - // Ignore identities that fail to restore. Either they were added before we set - // `saveIdentityCaps` to true, or they have lost access to the board. - return undefined; - } - }), - ); - const resolvedUsers = maybeUsers.filter(u => !!u); - const event = { path, type, users: resolvedUsers }; - if (caption) { - event.notification = { caption }; - } - await session.activity(event); + Meteor.wrapAsync(done => { + return Promise.all( + users.map(user => { + return httpBridge + .getSavedIdentity(user.id) + .then(response => { + // Call getProfile() to make sure that the identity successfully resolves. + // (In C++ we would instead call whenResolved() here.) + const identity = response.identity; + return identity.getProfile().then(() => { + return { + identity, + mentioned: !!user.mentioned, + subscribed: !!user.subscribed, + }; + }); + }) + .catch(() => { + // Ignore identities that fail to restore. Either they were added before we set + // `saveIdentityCaps` to true, or they have lost access to the board. + }); + }), + ) + .then(maybeUsers => { + const users = maybeUsers.filter(u => !!u); + const event = { path, type, users }; + if (caption) { + event.notification = { caption }; + } + + return session.activity(event); + }) + .then( + () => done(), + e => done(e), + ); + })(); } Meteor.startup(() => { - Activities.after.insert(async (userId, doc) => { + Activities.after.insert((userId, doc) => { // HACK: We need the connection that's making the request in order to read the // Sandstorm session ID. const invocation = DDP._CurrentInvocation.get(); // eslint-disable-line no-undef @@ -154,9 +176,9 @@ if (isSandstorm && Meteor.isServer) { ); if (defIdx >= 0) { const users = {}; - async function ensureUserListed(userId) { + function ensureUserListed(userId) { if (!users[userId]) { - const user = await Meteor.users.findOneAsync(userId); + const user = Meteor.users.findOne(userId); if (user) { users[userId] = { id: user.services.sandstorm.id }; } else { @@ -166,14 +188,14 @@ if (isSandstorm && Meteor.isServer) { return true; } - async function mentionedUser(userId) { - if (await ensureUserListed(userId)) { + function mentionedUser(userId) { + if (ensureUserListed(userId)) { users[userId].mentioned = true; } } - async function subscribedUser(userId) { - if (await ensureUserListed(userId)) { + function subscribedUser(userId) { + if (ensureUserListed(userId)) { users[userId].subscribed = true; } } @@ -183,16 +205,11 @@ if (isSandstorm && Meteor.isServer) { if (doc.cardId) { path = `b/sandstorm/libreboard/${doc.cardId}`; - const card = ReactiveCache.getCard(doc.cardId); - if (card && card.members) { - for (const memberId of card.members) { - await subscribedUser(memberId); - } - } + ReactiveCache.getCard(doc.cardId).members.map(subscribedUser); } if (doc.memberId) { - await mentionedUser(doc.memberId); + mentionedUser(doc.memberId); } if (doc.activityType === 'addComment') { @@ -202,24 +219,23 @@ if (isSandstorm && Meteor.isServer) { ReactiveCache.getBoard(sandstormBoard._id).activeMembers(), 'userId', ); - const mentions = comment.text.match(/\B@([\w.]*)/g) || []; - for (const username of mentions) { - const user = await Meteor.users.findOneAsync({ + (comment.text.match(/\B@([\w.]*)/g) || []).forEach(username => { + const user = Meteor.users.findOne({ username: username.slice(1), }); if (user && activeMembers.indexOf(user._id) !== -1) { - await mentionedUser(user._id); + mentionedUser(user._id); } - } + }); } - await reportActivity(sessionId, path, defIdx, _.values(users), caption); + reportActivity(sessionId, path, defIdx, _.values(users), caption); } } }); }); - async function updateUserPermissions(userId, permissions) { + function updateUserPermissions(userId, permissions) { const isActive = permissions.indexOf('participate') > -1; const isAdmin = permissions.indexOf('configure') > -1; const isCommentOnly = false; @@ -243,7 +259,7 @@ if (isSandstorm && Meteor.isServer) { else if (!isActive) modifier = {}; else modifier = { $push: { members: permissionDoc } }; - await Boards.updateAsync(sandstormBoard._id, modifier); + Boards.update(sandstormBoard._id, modifier); } Picker.route('/', (params, req, res) => { @@ -271,14 +287,14 @@ if (isSandstorm && Meteor.isServer) { // unique board document. Note that when the `Users.after.insert` hook is // called, the user is inserted into the database but not connected. So // despite the appearances `userId` is null in this block. - Users.after.insert(async (userId, doc) => { + Users.after.insert((userId, doc) => { if (!ReactiveCache.getBoard(sandstormBoard._id)) { - await Boards.insertAsync(sandstormBoard, { validate: false }); - await Swimlanes.insertAsync({ + Boards.insert(sandstormBoard, { validate: false }); + Swimlanes.insert({ title: 'Default', boardId: sandstormBoard._id, }); - await Activities.updateAsync( + Activities.update( { activityTypeId: sandstormBoard._id }, { $set: { userId: doc._id } }, ); @@ -296,7 +312,7 @@ if (isSandstorm && Meteor.isServer) { const username = doc.services.sandstorm.preferredHandle; let appendNumber = 0; while ( - await Meteor.users.findOneAsync({ + ReactiveCache.getUser({ _id: { $ne: doc._id }, username: generateUniqueUsername(username, appendNumber), }) @@ -304,7 +320,7 @@ if (isSandstorm && Meteor.isServer) { appendNumber += 1; } - await Users.updateAsync(doc._id, { + Users.update(doc._id, { $set: { username: generateUniqueUsername(username, appendNumber), 'profile.fullname': doc.services.sandstorm.name, @@ -312,27 +328,27 @@ if (isSandstorm && Meteor.isServer) { }, }); - await updateUserPermissions(doc._id, doc.services.sandstorm.permissions); + updateUserPermissions(doc._id, doc.services.sandstorm.permissions); }); - Meteor.startup(async () => { - await Users.find().observeChangesAsync({ - async changed(userId, fields) { + Meteor.startup(() => { + Users.find().observeChanges({ + changed(userId, fields) { const sandstormData = (fields.services || {}).sandstorm || {}; if (sandstormData.name) { - await Users.updateAsync(userId, { + Users.update(userId, { $set: { 'profile.fullname': sandstormData.name }, }); } if (sandstormData.picture) { - await Users.updateAsync(userId, { + Users.update(userId, { $set: { 'profile.avatarUrl': sandstormData.picture }, }); } if (sandstormData.permissions) { - await updateUserPermissions(userId, sandstormData.permissions); + updateUserPermissions(userId, sandstormData.permissions); } }, }); @@ -356,9 +372,9 @@ if (isSandstorm && Meteor.isServer) { HTTP.methods = newMethods => { Object.keys(newMethods).forEach(key => { if (newMethods[key].auth) { - newMethods[key].auth = async function() { + newMethods[key].auth = function() { const sandstormID = this.req.headers['x-sandstorm-user-id']; - const user = await Meteor.users.findOneAsync({ + const user = Meteor.users.findOne({ 'services.sandstorm.id': sandstormID, }); return user && user._id; @@ -462,7 +478,7 @@ if (isSandstorm && Meteor.isClient) { ]); Tracker.autorun(() => { - updateSandstormMetaData({ setTitle: document.title }); + updateSandstormMetaData({ setTitle: DocHead.getTitle() }); }); // Runtime redirection from the home page to the unique board -- since the diff --git a/server/00checkStartup.js b/server/00checkStartup.js index 6fc4ca13f..ed4dbabb3 100644 --- a/server/00checkStartup.js +++ b/server/00checkStartup.js @@ -4,7 +4,7 @@ const os = require('os'); // Configure SyncedCron to suppress console logging // This must be done before any SyncedCron operations if (Meteor.isServer) { - const { SyncedCron } = require('meteor/quave:synced-cron'); + const { SyncedCron } = require('meteor/percolate:synced-cron'); SyncedCron.config({ log: false, // Disable console logging collectionName: 'cronJobs', // Use custom collection name @@ -38,20 +38,16 @@ if (errors.length > 0) { } // Import cron job storage for persistent job tracking -// import './cronJobStorage'; +import './cronJobStorage'; -// Import migrations - COMMENTED OUT -// import './migrations/fixMissingListsMigration'; -// import './migrations/fixAvatarUrls'; -// import './migrations/fixAllFileUrls'; -// import './migrations/comprehensiveBoardMigration'; +// Import migrations +import './migrations/fixMissingListsMigration'; +import './migrations/fixAvatarUrls'; +import './migrations/fixAllFileUrls'; +import './migrations/comprehensiveBoardMigration'; // Import file serving routes import './routes/universalFileServer'; -import './routes/customHeadAssets'; - -// Import server-side custom head rendering -import './lib/customHeadRender'; // Note: Automatic migrations are disabled - migrations only run when opening boards // import './boardMigrationDetector'; diff --git a/server/accounts-lockout-config.js b/server/accounts-lockout-config.js index e14322ae7..af437c25b 100644 --- a/server/accounts-lockout-config.js +++ b/server/accounts-lockout-config.js @@ -1,21 +1,21 @@ import { AccountsLockout } from 'meteor/wekan-accounts-lockout'; import LockoutSettings from '/models/lockoutSettings'; -Meteor.startup(async () => { +Meteor.startup(() => { // Wait for the database to be ready - Meteor.setTimeout(async () => { + Meteor.setTimeout(() => { try { // Get configurations from database const knownUsersConfig = { - failuresBeforeLockout: (await LockoutSettings.findOneAsync('known-failuresBeforeLockout'))?.value || 3, - lockoutPeriod: (await LockoutSettings.findOneAsync('known-lockoutPeriod'))?.value || 60, - failureWindow: (await LockoutSettings.findOneAsync('known-failureWindow'))?.value || 15 + failuresBeforeLockout: LockoutSettings.findOne('known-failuresBeforeLockout')?.value || 3, + lockoutPeriod: LockoutSettings.findOne('known-lockoutPeriod')?.value || 60, + failureWindow: LockoutSettings.findOne('known-failureWindow')?.value || 15 }; const unknownUsersConfig = { - failuresBeforeLockout: (await LockoutSettings.findOneAsync('unknown-failuresBeforeLockout'))?.value || 3, - lockoutPeriod: (await LockoutSettings.findOneAsync('unknown-lockoutPeriod'))?.value || 60, - failureWindow: (await LockoutSettings.findOneAsync('unknown-failureWindow'))?.value || 15 + failuresBeforeLockout: LockoutSettings.findOne('unknown-failuresBeforeLockout')?.value || 3, + lockoutPeriod: LockoutSettings.findOne('unknown-lockoutPeriod')?.value || 60, + failureWindow: LockoutSettings.findOne('unknown-failureWindow')?.value || 15 }; // Initialize the AccountsLockout with configuration diff --git a/server/attachmentApi.js b/server/attachmentApi.js index e1d46003d..148753548 100644 --- a/server/attachmentApi.js +++ b/server/attachmentApi.js @@ -12,7 +12,7 @@ import { ObjectID } from 'bson'; if (Meteor.isServer) { Meteor.methods({ // Upload attachment via API - async 'api.attachment.upload'(boardId, swimlaneId, listId, cardId, fileData, fileName, fileType, storageBackend) { + 'api.attachment.upload'(boardId, swimlaneId, listId, cardId, fileData, fileName, fileType, storageBackend) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } @@ -23,12 +23,12 @@ if (Meteor.isServer) { } // Check if user has permission to modify the card - const card = await ReactiveCache.getCard(cardId); + const card = ReactiveCache.getCard(cardId); if (!card) { throw new Meteor.Error('card-not-found', 'Card not found'); } - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (!board) { throw new Meteor.Error('board-not-found', 'Board not found'); } @@ -114,19 +114,19 @@ if (Meteor.isServer) { }, // Download attachment via API - async 'api.attachment.download'(attachmentId) { + 'api.attachment.download'(attachmentId) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } // Get attachment - const attachment = await ReactiveCache.getAttachment(attachmentId); + const attachment = ReactiveCache.getAttachment(attachmentId); if (!attachment) { throw new Meteor.Error('attachment-not-found', 'Attachment not found'); } // Check permissions - const board = await ReactiveCache.getBoard(attachment.meta.boardId); + const board = ReactiveCache.getBoard(attachment.meta.boardId); if (!board || !board.isBoardMember(this.userId)) { throw new Meteor.Error('not-authorized', 'You do not have permission to access this attachment'); } @@ -150,7 +150,7 @@ if (Meteor.isServer) { readStream.on('end', () => { const fileBuffer = Buffer.concat(chunks); const base64Data = fileBuffer.toString('base64'); - + resolve({ success: true, attachmentId: attachmentId, @@ -173,13 +173,13 @@ if (Meteor.isServer) { }, // List attachments for board, swimlane, list, or card - async 'api.attachment.list'(boardId, swimlaneId, listId, cardId) { + 'api.attachment.list'(boardId, swimlaneId, listId, cardId) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } // Check permissions - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (!board || !board.isBoardMember(this.userId)) { throw new Meteor.Error('not-authorized', 'You do not have permission to access this board'); } @@ -199,7 +199,7 @@ if (Meteor.isServer) { query['meta.cardId'] = cardId; } - const attachments = await ReactiveCache.getAttachments(query); + const attachments = ReactiveCache.getAttachments(query); const attachmentList = attachments.map(attachment => { const strategy = fileStoreStrategyFactory.getFileStrategy(attachment, 'original'); @@ -230,25 +230,25 @@ if (Meteor.isServer) { }, // Copy attachment to another card - async 'api.attachment.copy'(attachmentId, targetBoardId, targetSwimlaneId, targetListId, targetCardId) { + 'api.attachment.copy'(attachmentId, targetBoardId, targetSwimlaneId, targetListId, targetCardId) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } // Get source attachment - const sourceAttachment = await ReactiveCache.getAttachment(attachmentId); + const sourceAttachment = ReactiveCache.getAttachment(attachmentId); if (!sourceAttachment) { throw new Meteor.Error('attachment-not-found', 'Source attachment not found'); } // Check source permissions - const sourceBoard = await ReactiveCache.getBoard(sourceAttachment.meta.boardId); + const sourceBoard = ReactiveCache.getBoard(sourceAttachment.meta.boardId); if (!sourceBoard || !sourceBoard.isBoardMember(this.userId)) { throw new Meteor.Error('not-authorized', 'You do not have permission to access the source attachment'); } // Check target permissions - const targetBoard = await ReactiveCache.getBoard(targetBoardId); + const targetBoard = ReactiveCache.getBoard(targetBoardId); if (!targetBoard || !targetBoard.isBoardMember(this.userId)) { throw new Meteor.Error('not-authorized', 'You do not have permission to modify the target card'); } @@ -328,25 +328,25 @@ if (Meteor.isServer) { }, // Move attachment to another card - async 'api.attachment.move'(attachmentId, targetBoardId, targetSwimlaneId, targetListId, targetCardId) { + 'api.attachment.move'(attachmentId, targetBoardId, targetSwimlaneId, targetListId, targetCardId) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } // Get source attachment - const sourceAttachment = await ReactiveCache.getAttachment(attachmentId); + const sourceAttachment = ReactiveCache.getAttachment(attachmentId); if (!sourceAttachment) { throw new Meteor.Error('attachment-not-found', 'Source attachment not found'); } // Check source permissions - const sourceBoard = await ReactiveCache.getBoard(sourceAttachment.meta.boardId); + const sourceBoard = ReactiveCache.getBoard(sourceAttachment.meta.boardId); if (!sourceBoard || !sourceBoard.isBoardMember(this.userId)) { throw new Meteor.Error('not-authorized', 'You do not have permission to access the source attachment'); } // Check target permissions - const targetBoard = await ReactiveCache.getBoard(targetBoardId); + const targetBoard = ReactiveCache.getBoard(targetBoardId); if (!targetBoard || !targetBoard.isBoardMember(this.userId)) { throw new Meteor.Error('not-authorized', 'You do not have permission to modify the target card'); } @@ -385,19 +385,19 @@ if (Meteor.isServer) { }, // Delete attachment via API - async 'api.attachment.delete'(attachmentId) { + 'api.attachment.delete'(attachmentId) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } // Get attachment - const attachment = await ReactiveCache.getAttachment(attachmentId); + const attachment = ReactiveCache.getAttachment(attachmentId); if (!attachment) { throw new Meteor.Error('attachment-not-found', 'Attachment not found'); } // Check permissions - const board = await ReactiveCache.getBoard(attachment.meta.boardId); + const board = ReactiveCache.getBoard(attachment.meta.boardId); if (!board || !board.isBoardMember(this.userId)) { throw new Meteor.Error('not-authorized', 'You do not have permission to delete this attachment'); } @@ -419,26 +419,26 @@ if (Meteor.isServer) { }, // Get attachment info via API - async 'api.attachment.info'(attachmentId) { + 'api.attachment.info'(attachmentId) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } // Get attachment - const attachment = await ReactiveCache.getAttachment(attachmentId); + const attachment = ReactiveCache.getAttachment(attachmentId); if (!attachment) { throw new Meteor.Error('attachment-not-found', 'Attachment not found'); } // Check permissions - const board = await ReactiveCache.getBoard(attachment.meta.boardId); + const board = ReactiveCache.getBoard(attachment.meta.boardId); if (!board || !board.isBoardMember(this.userId)) { throw new Meteor.Error('not-authorized', 'You do not have permission to access this attachment'); } try { const strategy = fileStoreStrategyFactory.getFileStrategy(attachment, 'original'); - + return { success: true, attachmentId: attachment._id, diff --git a/server/attachmentMigration.js b/server/attachmentMigration.js index c436ddcfd..d769dde92 100644 --- a/server/attachmentMigration.js +++ b/server/attachmentMigration.js @@ -8,7 +8,6 @@ import { ReactiveVar } from 'meteor/reactive-var'; import { check } from 'meteor/check'; import { ReactiveCache } from '/imports/reactiveCache'; import Attachments from '/models/attachments'; -import { AttachmentMigrationStatus } from './attachmentMigrationStatus'; // Reactive variables for tracking migration progress const migrationProgress = new ReactiveVar(0); @@ -29,21 +28,7 @@ class AttachmentMigrationService { * @returns {boolean} - True if board has been migrated */ isBoardMigrated(boardId) { - const isMigrated = migratedBoards.has(boardId); - - // Update status collection for pub/sub - AttachmentMigrationStatus.upsert( - { boardId }, - { - $set: { - boardId, - isMigrated, - updatedAt: new Date() - } - } - ); - - return isMigrated; + return migratedBoards.has(boardId); } /** @@ -59,7 +44,7 @@ class AttachmentMigrationService { } console.log(`Starting attachment migration for board: ${boardId}`); - + // Get all attachments for the board const attachments = Attachments.find({ 'meta.boardId': boardId @@ -78,12 +63,12 @@ class AttachmentMigrationService { await this.migrateAttachment(attachment); this.migrationCache.set(attachment._id, true); } - + migratedCount++; const progress = Math.round((migratedCount / totalAttachments) * 100); migrationProgress.set(progress); migrationStatus.set(`Migrated ${migratedCount}/${totalAttachments} attachments...`); - + } catch (error) { console.error(`Error migrating attachment ${attachment._id}:`, error); } @@ -101,23 +86,6 @@ class AttachmentMigrationService { console.log(`Attachment migration completed for board: ${boardId}`); console.log(`Marked board ${boardId} as migrated`); - // Update status collection - AttachmentMigrationStatus.upsert( - { boardId }, - { - $set: { - boardId, - isMigrated: true, - totalAttachments, - migratedAttachments: totalAttachments, - unconvertedAttachments: 0, - progress: 100, - status: 'completed', - updatedAt: new Date() - } - } - ); - return { success: true, message: 'Migration completed' }; } catch (error) { @@ -138,8 +106,8 @@ class AttachmentMigrationService { } // Check if attachment has old structure - return !attachment.meta || - !attachment.meta.cardId || + return !attachment.meta || + !attachment.meta.cardId || !attachment.meta.boardId || !attachment.meta.listId; } @@ -151,13 +119,13 @@ class AttachmentMigrationService { async migrateAttachment(attachment) { try { // Get the card to find board and list information - const card = await ReactiveCache.getCard(attachment.cardId); + const card = ReactiveCache.getCard(attachment.cardId); if (!card) { console.warn(`Card not found for attachment ${attachment._id}`); return; } - const list = await ReactiveCache.getList(card.listId); + const list = ReactiveCache.getList(card.listId); if (!list) { console.warn(`List not found for attachment ${attachment._id}`); return; @@ -220,25 +188,6 @@ class AttachmentMigrationService { const progress = migrationProgress.get(); const status = migrationStatus.get(); const unconverted = this.getUnconvertedAttachments(boardId); - const total = Attachments.find({ 'meta.boardId': boardId }).count(); - const migratedCount = total - unconverted.length; - - // Update status collection for pub/sub - AttachmentMigrationStatus.upsert( - { boardId }, - { - $set: { - boardId, - totalAttachments: total, - migratedAttachments: migratedCount, - unconvertedAttachments: unconverted.length, - progress: total > 0 ? Math.round((migratedCount / total) * 100) : 0, - status: status || 'idle', - isMigrated: unconverted.length === 0, - updatedAt: new Date() - } - } - ); return { progress, @@ -254,69 +203,41 @@ const attachmentMigrationService = new AttachmentMigrationService(); Meteor.methods({ async 'attachmentMigration.migrateBoardAttachments'(boardId) { check(boardId, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - const board = await ReactiveCache.getBoard(boardId); - if (!board) { - throw new Meteor.Error('board-not-found'); - } - - const user = await ReactiveCache.getUser(this.userId); - const isBoardAdmin = board.hasAdmin(this.userId); - const isInstanceAdmin = user && user.isAdmin; - - if (!isBoardAdmin && !isInstanceAdmin) { - throw new Meteor.Error('not-authorized', 'You must be a board admin or instance admin to perform this action.'); - } - return await attachmentMigrationService.migrateBoardAttachments(boardId); }, - async 'attachmentMigration.getProgress'(boardId) { + 'attachmentMigration.getProgress'(boardId) { check(boardId, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - const board = await ReactiveCache.getBoard(boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - return attachmentMigrationService.getMigrationProgress(boardId); }, - async 'attachmentMigration.getUnconvertedAttachments'(boardId) { + 'attachmentMigration.getUnconvertedAttachments'(boardId) { check(boardId, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - const board = await ReactiveCache.getBoard(boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - return attachmentMigrationService.getUnconvertedAttachments(boardId); }, - async 'attachmentMigration.isBoardMigrated'(boardId) { + 'attachmentMigration.isBoardMigrated'(boardId) { check(boardId, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - const board = await ReactiveCache.getBoard(boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - return attachmentMigrationService.isBoardMigrated(boardId); } }); diff --git a/server/attachmentMigrationStatus.js b/server/attachmentMigrationStatus.js deleted file mode 100644 index f690293ab..000000000 --- a/server/attachmentMigrationStatus.js +++ /dev/null @@ -1,22 +0,0 @@ -import { Mongo } from 'meteor/mongo'; - -// Server-side collection for attachment migration status -export const AttachmentMigrationStatus = new Mongo.Collection('attachmentMigrationStatus'); - -// Allow/Deny rules -// This collection is server-only and should not be modified by clients -// Allow server-side operations (when userId is undefined) but deny all client operations -if (Meteor.isServer) { - AttachmentMigrationStatus.allow({ - insert: (userId) => !userId, - update: (userId) => !userId, - remove: (userId) => !userId, - }); -} - -// Create indexes for better query performance -Meteor.startup(() => { - AttachmentMigrationStatus._collection.createIndexAsync({ boardId: 1 }); - AttachmentMigrationStatus._collection.createIndexAsync({ userId: 1, boardId: 1 }); - AttachmentMigrationStatus._collection.createIndexAsync({ updatedAt: -1 }); -}); diff --git a/server/authentication.js b/server/authentication.js index 9309cd975..474de6e25 100644 --- a/server/authentication.js +++ b/server/authentication.js @@ -15,13 +15,13 @@ Meteor.startup(() => { Authentication = {}; - Authentication.checkUserId = async function(userId) { + Authentication.checkUserId = function(userId) { if (userId === undefined) { const error = new Meteor.Error('Unauthorized', 'Unauthorized'); error.statusCode = 401; throw error; } - const admin = await ReactiveCache.getUser({ _id: userId, isAdmin: true }); + const admin = ReactiveCache.getUser({ _id: userId, isAdmin: true }); if (admin === undefined) { const error = new Meteor.Error('Forbidden', 'Forbidden'); @@ -42,9 +42,9 @@ Meteor.startup(() => { // An admin should be authorized to access everything, so we use a separate check for admins // This throws an error if otherReq is false and the user is not an admin - Authentication.checkAdminOrCondition = async function(userId, otherReq) { + Authentication.checkAdminOrCondition = function(userId, otherReq) { if (otherReq) return; - const admin = await ReactiveCache.getUser({ _id: userId, isAdmin: true }); + const admin = ReactiveCache.getUser({ _id: userId, isAdmin: true }); if (admin === undefined) { const error = new Meteor.Error('Forbidden', 'Forbidden'); error.statusCode = 403; @@ -53,27 +53,11 @@ Meteor.startup(() => { }; // Helper function. Will throw an error if the user is not active BoardAdmin or active Normal user of the board. - Authentication.checkBoardAccess = async function(userId, boardId) { + Authentication.checkBoardAccess = function(userId, boardId) { Authentication.checkLoggedIn(userId); - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); const normalAccess = board.members.some(e => e.userId === userId && e.isActive && !e.isNoComments && !e.isCommentOnly && !e.isWorker); - await Authentication.checkAdminOrCondition(userId, normalAccess); - }; - - // Helper function. Will throw an error if the user does not have write access to the board (excludes read-only users). - Authentication.checkBoardWriteAccess = async function(userId, boardId) { - Authentication.checkLoggedIn(userId); - const board = await ReactiveCache.getBoard(boardId); - const writeAccess = board.members.some(e => e.userId === userId && e.isActive && !e.isNoComments && !e.isCommentOnly && !e.isWorker && !e.isReadOnly && !e.isReadAssignedOnly); - await Authentication.checkAdminOrCondition(userId, writeAccess); - }; - - // Helper function. Will throw an error if the user is not a board admin. - Authentication.checkBoardAdmin = async function(userId, boardId) { - Authentication.checkLoggedIn(userId); - const board = await ReactiveCache.getBoard(boardId); - const adminAccess = board.members.some(e => e.userId === userId && e.isActive && e.isAdmin); - await Authentication.checkAdminOrCondition(userId, adminAccess); + Authentication.checkAdminOrCondition(userId, normalAccess); }; if (Meteor.isServer) { diff --git a/server/boardMigrationDetector.js b/server/boardMigrationDetector.js index 7d1a78dce..dac558e5d 100644 --- a/server/boardMigrationDetector.js +++ b/server/boardMigrationDetector.js @@ -63,7 +63,7 @@ class BoardMigrationDetector { isSystemIdle() { const resources = cronJobStorage.getSystemResources(); const queueStats = cronJobStorage.getQueueStats(); - + // Check if no jobs are running if (queueStats.running > 0) { return false; @@ -120,7 +120,7 @@ class BoardMigrationDetector { try { // Scanning for unmigrated boards - + // Get all boards from the database const boards = this.getAllBoards(); const unmigrated = []; @@ -155,7 +155,7 @@ class BoardMigrationDetector { if (typeof Boards !== 'undefined') { return Boards.find({}, { fields: { _id: 1, title: 1, createdAt: 1, modifiedAt: 1 } }).fetch(); } - + // Fallback: return empty array if Boards collection not available return []; } catch (error) { @@ -171,14 +171,14 @@ class BoardMigrationDetector { try { // Check if board has been migrated by looking for migration markers const migrationMarkers = this.getMigrationMarkers(board._id); - + // Check for specific migration indicators const needsListMigration = !migrationMarkers.listsMigrated; const needsAttachmentMigration = !migrationMarkers.attachmentsMigrated; const needsSwimlaneMigration = !migrationMarkers.swimlanesMigrated; - + return needsListMigration || needsAttachmentMigration || needsSwimlaneMigration; - + } catch (error) { console.error(`Error checking migration status for board ${board._id}:`, error); return false; @@ -192,7 +192,7 @@ class BoardMigrationDetector { try { // Check if board has migration metadata const board = Boards.findOne(boardId, { fields: { migrationMarkers: 1 } }); - + if (!board || !board.migrationMarkers) { return { listsMigrated: false, @@ -230,7 +230,7 @@ class BoardMigrationDetector { // Create migration job for this board const jobId = `board_migration_${board._id}_${Date.now()}`; - + // Add to job queue with high priority cronJobStorage.addToQueue(jobId, 'board_migration', 1, { boardId: board._id, @@ -292,14 +292,14 @@ class BoardMigrationDetector { getBoardMigrationStatus(boardId) { const unmigrated = unmigratedBoards.get(); const isUnmigrated = unmigrated.some(b => b._id === boardId); - + if (!isUnmigrated) { return { needsMigration: false, reason: 'Board is already migrated' }; } const migrationMarkers = this.getMigrationMarkers(boardId); - const needsMigration = !migrationMarkers.listsMigrated || - !migrationMarkers.attachmentsMigrated || + const needsMigration = !migrationMarkers.listsMigrated || + !migrationMarkers.attachmentsMigrated || !migrationMarkers.swimlanesMigrated; return { @@ -352,7 +352,7 @@ Meteor.methods({ if (!this.userId) { throw new Meteor.Error('not-authorized'); } - + return boardMigrationDetector.getMigrationStats(); }, @@ -360,38 +360,38 @@ Meteor.methods({ if (!this.userId) { throw new Meteor.Error('not-authorized'); } - + return boardMigrationDetector.forceScan(); }, 'boardMigration.getBoardStatus'(boardId) { check(boardId, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - + return boardMigrationDetector.getBoardMigrationStatus(boardId); }, 'boardMigration.markAsMigrated'(boardId, migrationType) { check(boardId, String); check(migrationType, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - + return boardMigrationDetector.markBoardAsMigrated(boardId, migrationType); }, 'boardMigration.startBoardMigration'(boardId) { check(boardId, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - + return boardMigrationDetector.startBoardMigration(boardId); }, @@ -399,7 +399,7 @@ Meteor.methods({ if (!this.userId) { throw new Meteor.Error('not-authorized'); } - + // Find boards that have migration markers but no migrationVersion const stuckBoards = Boards.find({ 'migrationMarkers.fullMigrationCompleted': true, @@ -408,15 +408,15 @@ Meteor.methods({ { migrationVersion: { $lt: 1 } } ] }).fetch(); - + let fixedCount = 0; stuckBoards.forEach(board => { try { - Boards.update(board._id, { - $set: { + Boards.update(board._id, { + $set: { migrationVersion: 1, 'migrationMarkers.lastMigration': new Date() - } + } }); fixedCount++; console.log(`Fixed stuck board: ${board._id} (${board.title})`); @@ -424,7 +424,7 @@ Meteor.methods({ console.error(`Error fixing board ${board._id}:`, error); } }); - + return { message: `Fixed ${fixedCount} stuck boards`, fixedCount, diff --git a/server/cronJobStorage.js b/server/cronJobStorage.js index e12c25ca5..18c0b0150 100644 --- a/server/cronJobStorage.js +++ b/server/cronJobStorage.js @@ -10,64 +10,25 @@ import { Mongo } from 'meteor/mongo'; export const CronJobStatus = new Mongo.Collection('cronJobStatus'); export const CronJobSteps = new Mongo.Collection('cronJobSteps'); export const CronJobQueue = new Mongo.Collection('cronJobQueue'); -export const CronJobErrors = new Mongo.Collection('cronJobErrors'); - -// Allow/Deny rules -// These collections are server-only and should not be modified by clients -// Allow server-side operations (when userId is undefined) but deny all client operations -if (Meteor.isServer) { - // Helper function to check if operation is server-only - const isServerOperation = (userId) => !userId; - - CronJobStatus.allow({ - insert: isServerOperation, - update: isServerOperation, - remove: isServerOperation, - }); - - CronJobSteps.allow({ - insert: isServerOperation, - update: isServerOperation, - remove: isServerOperation, - }); - - CronJobQueue.allow({ - insert: isServerOperation, - update: isServerOperation, - remove: isServerOperation, - }); - - CronJobErrors.allow({ - insert: isServerOperation, - update: isServerOperation, - remove: isServerOperation, - }); -} // Indexes for performance if (Meteor.isServer) { - Meteor.startup(async () => { + Meteor.startup(() => { // Index for job status queries - await CronJobStatus._collection.createIndexAsync({ jobId: 1 }); - await CronJobStatus._collection.createIndexAsync({ status: 1 }); - await CronJobStatus._collection.createIndexAsync({ createdAt: 1 }); - await CronJobStatus._collection.createIndexAsync({ updatedAt: 1 }); - + CronJobStatus._collection.createIndex({ jobId: 1 }); + CronJobStatus._collection.createIndex({ status: 1 }); + CronJobStatus._collection.createIndex({ createdAt: 1 }); + CronJobStatus._collection.createIndex({ updatedAt: 1 }); + // Index for job steps queries - await CronJobSteps._collection.createIndexAsync({ jobId: 1 }); - await CronJobSteps._collection.createIndexAsync({ stepIndex: 1 }); - await CronJobSteps._collection.createIndexAsync({ status: 1 }); - + CronJobSteps._collection.createIndex({ jobId: 1 }); + CronJobSteps._collection.createIndex({ stepIndex: 1 }); + CronJobSteps._collection.createIndex({ status: 1 }); + // Index for job queue queries - await CronJobQueue._collection.createIndexAsync({ priority: 1, createdAt: 1 }); - await CronJobQueue._collection.createIndexAsync({ status: 1 }); - await CronJobQueue._collection.createIndexAsync({ jobType: 1 }); - - // Index for job errors queries - await CronJobErrors._collection.createIndexAsync({ jobId: 1, createdAt: -1 }); - await CronJobErrors._collection.createIndexAsync({ stepId: 1 }); - await CronJobErrors._collection.createIndexAsync({ severity: 1 }); - await CronJobErrors._collection.createIndexAsync({ createdAt: -1 }); + CronJobQueue._collection.createIndex({ priority: 1, createdAt: 1 }); + CronJobQueue._collection.createIndex({ status: 1 }); + CronJobQueue._collection.createIndex({ jobType: 1 }); }); } @@ -87,7 +48,7 @@ class CronJobStorage { if (envLimit) { return parseInt(envLimit, 10); } - + // Auto-detect based on CPU cores const os = require('os'); const cpuCores = os.cpus().length; @@ -100,7 +61,7 @@ class CronJobStorage { saveJobStatus(jobId, jobData) { const now = new Date(); const existingJob = CronJobStatus.findOne({ jobId }); - + if (existingJob) { CronJobStatus.update( { jobId }, @@ -143,7 +104,7 @@ class CronJobStorage { saveJobStep(jobId, stepIndex, stepData) { const now = new Date(); const existingStep = CronJobSteps.findOne({ jobId, stepIndex }); - + if (existingStep) { CronJobSteps.update( { jobId, stepIndex }, @@ -185,71 +146,18 @@ class CronJobStorage { }, { sort: { stepIndex: 1 } }).fetch(); } - /** - * Save job error to persistent storage - */ - saveJobError(jobId, errorData) { - const now = new Date(); - const { stepId, stepIndex, error, severity = 'error', context = {} } = errorData; - - CronJobErrors.insert({ - jobId, - stepId, - stepIndex, - errorMessage: typeof error === 'string' ? error : error.message || 'Unknown error', - errorStack: error.stack || null, - severity, - context, - createdAt: now - }); - } - - /** - * Get job errors from persistent storage - */ - getJobErrors(jobId, options = {}) { - const { limit = 100, severity = null } = options; - - const query = { jobId }; - if (severity) { - query.severity = severity; - } - - return CronJobErrors.find(query, { - sort: { createdAt: -1 }, - limit - }).fetch(); - } - - /** - * Get all recent errors across all jobs - */ - getAllRecentErrors(limit = 50) { - return CronJobErrors.find({}, { - sort: { createdAt: -1 }, - limit - }).fetch(); - } - - /** - * Clear errors for a specific job - */ - clearJobErrors(jobId) { - return CronJobErrors.remove({ jobId }); - } - /** * Add job to queue */ addToQueue(jobId, jobType, priority = 5, jobData = {}) { const now = new Date(); - + // Check if job already exists in queue const existingJob = CronJobQueue.findOne({ jobId }); if (existingJob) { return existingJob._id; } - + return CronJobQueue.insert({ jobId, jobType, @@ -301,26 +209,26 @@ class CronJobStorage { */ getSystemResources() { const os = require('os'); - + // Get CPU usage (simplified) const cpus = os.cpus(); let totalIdle = 0; let totalTick = 0; - + cpus.forEach(cpu => { for (const type in cpu.times) { totalTick += cpu.times[type]; } totalIdle += cpu.times.idle; }); - + const cpuUsage = 100 - Math.round(100 * totalIdle / totalTick); - + // Get memory usage const totalMem = os.totalmem(); const freeMem = os.freemem(); const memoryUsage = Math.round(100 * (totalMem - freeMem) / totalMem); - + return { cpuUsage, memoryUsage, @@ -336,21 +244,21 @@ class CronJobStorage { canStartNewJob() { const resources = this.getSystemResources(); const runningJobs = CronJobQueue.find({ status: 'running' }).count(); - + // Check CPU and memory thresholds if (resources.cpuUsage > this.cpuThreshold) { return { canStart: false, reason: 'CPU usage too high' }; } - + if (resources.memoryUsage > this.memoryThreshold) { return { canStart: false, reason: 'Memory usage too high' }; } - + // Check concurrent job limit if (runningJobs >= this.maxConcurrentJobs) { return { canStart: false, reason: 'Maximum concurrent jobs reached' }; } - + return { canStart: true, reason: 'System can handle new job' }; } @@ -363,7 +271,7 @@ class CronJobStorage { const running = CronJobQueue.find({ status: 'running' }).count(); const completed = CronJobQueue.find({ status: 'completed' }).count(); const failed = CronJobQueue.find({ status: 'failed' }).count(); - + return { total, pending, @@ -380,25 +288,25 @@ class CronJobStorage { cleanupOldJobs(daysOld = 7) { const cutoffDate = new Date(); cutoffDate.setDate(cutoffDate.getDate() - daysOld); - + // Remove old completed jobs from queue const removedQueue = CronJobQueue.remove({ status: 'completed', updatedAt: { $lt: cutoffDate } }); - + // Remove old job statuses const removedStatus = CronJobStatus.remove({ status: 'completed', updatedAt: { $lt: cutoffDate } }); - + // Remove old job steps const removedSteps = CronJobSteps.remove({ status: 'completed', updatedAt: { $lt: cutoffDate } }); - + return { removedQueue, removedStatus, @@ -412,7 +320,7 @@ class CronJobStorage { resumeIncompleteJobs() { const incompleteJobs = this.getIncompleteJobs(); const resumedJobs = []; - + incompleteJobs.forEach(job => { // Reset running jobs to pending if (job.status === 'running') { @@ -423,14 +331,14 @@ class CronJobStorage { }); resumedJobs.push(job.jobId); } - + // Add to queue if not already there const queueJob = CronJobQueue.findOne({ jobId: job.jobId }); if (!queueJob) { this.addToQueue(job.jobId, job.jobType || 'unknown', job.priority || 5, job); } }); - + return resumedJobs; } @@ -440,7 +348,7 @@ class CronJobStorage { getJobProgress(jobId) { const steps = this.getJobSteps(jobId); if (steps.length === 0) return 0; - + const completedSteps = steps.filter(step => step.status === 'completed').length; return Math.round((completedSteps / steps.length) * 100); } @@ -452,7 +360,7 @@ class CronJobStorage { const jobStatus = this.getJobStatus(jobId); const jobSteps = this.getJobSteps(jobId); const progress = this.getJobProgress(jobId); - + return { ...jobStatus, steps: jobSteps, @@ -471,8 +379,7 @@ class CronJobStorage { CronJobStatus.remove({}); CronJobSteps.remove({}); CronJobQueue.remove({}); - CronJobErrors.remove({}); - + console.log('All cron job data cleared from storage'); return { success: true, message: 'All cron job data cleared' }; } catch (error) { @@ -492,7 +399,7 @@ Meteor.startup(() => { if (resumedJobs.length > 0) { // Resumed incomplete cron jobs } - + // Cleanup old jobs const cleanup = cronJobStorage.cleanupOldJobs(); if (cleanup.removedQueue > 0 || cleanup.removedStatus > 0 || cleanup.removedSteps > 0) { diff --git a/server/cronMigrationManager.js b/server/cronMigrationManager.js index 7ae295ffb..a830f4ab4 100644 --- a/server/cronMigrationManager.js +++ b/server/cronMigrationManager.js @@ -4,20 +4,9 @@ */ import { Meteor } from 'meteor/meteor'; -import { SyncedCron } from 'meteor/quave:synced-cron'; +import { SyncedCron } from 'meteor/percolate:synced-cron'; import { ReactiveVar } from 'meteor/reactive-var'; -import { check, Match } from 'meteor/check'; -import { ReactiveCache } from '/imports/reactiveCache'; -import { cronJobStorage, CronJobStatus } from './cronJobStorage'; -import Users from '/models/users'; -import Boards from '/models/boards'; -import Cards from '/models/cards'; -import Attachments from '/models/attachments'; -import Swimlanes from '/models/swimlanes'; -import Checklists from '/models/checklists'; -import { runEnsureValidSwimlaneIdsMigration } from './migrations/ensureValidSwimlaneIds'; -import { comprehensiveBoardMigration } from './migrations/comprehensiveBoardMigration'; - +import { cronJobStorage } from './cronJobStorage'; // Server-side reactive variables for cron migration progress export const cronMigrationProgress = new ReactiveVar(0); @@ -26,8 +15,6 @@ export const cronMigrationCurrentStep = new ReactiveVar(''); export const cronMigrationSteps = new ReactiveVar([]); export const cronIsMigrating = new ReactiveVar(false); export const cronJobs = new ReactiveVar([]); -export const cronMigrationCurrentStepNum = new ReactiveVar(0); -export const cronMigrationTotalSteps = new ReactiveVar(0); // Board-specific operation tracking export const boardOperations = new ReactiveVar(new Map()); @@ -41,8 +28,6 @@ class CronMigrationManager { this.isRunning = false; this.jobProcessor = null; this.processingInterval = null; - this.monitorInterval = null; - this.pausedJobs = new Map(); // Store paused job configs for per-job pause/resume } /** @@ -50,6 +35,39 @@ class CronMigrationManager { */ initializeMigrationSteps() { return [ + { + id: 'board-background-color', + name: 'Board Background Colors', + description: 'Setting up board background colors', + weight: 1, + completed: false, + progress: 0, + cronName: 'migration_board_background_color', + schedule: 'every 1 minute', // Will be changed to 'once' when triggered + status: 'stopped' + }, + { + id: 'add-cardcounterlist-allowed', + name: 'Card Counter List Settings', + description: 'Adding card counter list permissions', + weight: 1, + completed: false, + progress: 0, + cronName: 'migration_card_counter_list', + schedule: 'every 1 minute', + status: 'stopped' + }, + { + id: 'add-boardmemberlist-allowed', + name: 'Board Member List Settings', + description: 'Adding board member list permissions', + weight: 1, + completed: false, + progress: 0, + cronName: 'migration_board_member_list', + schedule: 'every 1 minute', + status: 'stopped' + }, { id: 'lowercase-board-permission', name: 'Board Permission Standardization', @@ -117,13 +135,13 @@ class CronMigrationManager { status: 'stopped' }, { - id: 'ensure-valid-swimlane-ids', - name: 'Validate Swimlane IDs', - description: 'Ensuring all cards and lists have valid swimlaneId references', + id: 'add-sort-checklists', + name: 'Checklist Sorting', + description: 'Adding sort order to checklists', weight: 2, completed: false, progress: 0, - cronName: 'migration_swimlane_ids', + cronName: 'migration_sort_checklists', schedule: 'every 1 minute', status: 'stopped' }, @@ -138,6 +156,17 @@ class CronMigrationManager { schedule: 'every 1 minute', status: 'stopped' }, + { + id: 'add-views', + name: 'Board Views', + description: 'Adding board view options', + weight: 2, + completed: false, + progress: 0, + cronName: 'migration_views', + schedule: 'every 1 minute', + status: 'stopped' + }, { id: 'add-checklist-items', name: 'Checklist Items', @@ -160,6 +189,17 @@ class CronMigrationManager { schedule: 'every 1 minute', status: 'stopped' }, + { + id: 'add-custom-fields-to-cards', + name: 'Custom Fields', + description: 'Adding custom fields to cards', + weight: 3, + completed: false, + progress: 0, + cronName: 'migration_custom_fields', + schedule: 'every 1 minute', + status: 'stopped' + }, { id: 'migrate-attachments-collectionFS-to-ostrioFiles', name: 'Migrate Attachments to Meteor-Files', @@ -203,10 +243,10 @@ class CronMigrationManager { this.migrationSteps.forEach(step => { this.createCronJob(step); }); - + // Start job processor this.startJobProcessor(); - + // Update cron jobs list after a short delay to allow SyncedCron to initialize Meteor.setTimeout(() => { this.updateCronJobsList(); @@ -243,7 +283,7 @@ class CronMigrationManager { */ async processJobQueue() { const canStart = cronJobStorage.canStartNewJob(); - + if (!canStart.canStart) { // Suppress "Cannot start new job: Maximum concurrent jobs reached" message // console.log(`Cannot start new job: ${canStart.reason}`); @@ -264,11 +304,11 @@ class CronMigrationManager { */ async executeJob(queueJob) { const { jobId, jobType, jobData } = queueJob; - + try { // Update queue status to running cronJobStorage.updateQueueStatus(jobId, 'running', { startedAt: new Date() }); - + // Save job status cronJobStorage.saveJobStatus(jobId, { jobType, @@ -299,11 +339,11 @@ class CronMigrationManager { } catch (error) { console.error(`Job ${jobId} failed:`, error); - + // Mark as failed - cronJobStorage.updateQueueStatus(jobId, 'failed', { + cronJobStorage.updateQueueStatus(jobId, 'failed', { failedAt: new Date(), - error: error.message + error: error.message }); cronJobStorage.saveJobStatus(jobId, { status: 'failed', @@ -320,12 +360,12 @@ class CronMigrationManager { if (!jobData) { throw new Error('Job data is required for migration execution'); } - + const { stepId } = jobData; if (!stepId) { throw new Error('Step ID is required in job data'); } - + const step = this.migrationSteps.find(s => s.id === stepId); if (!step) { throw new Error(`Migration step ${stepId} not found`); @@ -333,10 +373,10 @@ class CronMigrationManager { // Create steps for this migration const steps = this.createMigrationSteps(step); - + for (let i = 0; i < steps.length; i++) { const stepData = steps[i]; - + // Save step status cronJobStorage.saveJobStep(jobId, i, { stepName: stepData.name, @@ -365,7 +405,7 @@ class CronMigrationManager { */ createMigrationSteps(step) { const steps = []; - + switch (step.id) { case 'board-background-color': steps.push( @@ -396,1091 +436,39 @@ class CronMigrationManager { { name: 'Verify changes', duration: 1000 } ); } - + return steps; } - isMigrationNeeded(stepId) { - switch (stepId) { - case 'lowercase-board-permission': - return !!Boards.findOne({ - permission: { $in: ['PUBLIC', 'Private', 'PRIVATE'] } - }, { fields: { _id: 1 }, limit: 1 }); - case 'change-attachments-type-for-non-images': - return !!Attachments.findOne({ - $or: [ - { type: { $exists: false } }, - { type: null }, - { type: '' } - ] - }, { fields: { _id: 1 }, limit: 1 }); - case 'card-covers': - return !!Cards.findOne({ - coverId: { $exists: true, $ne: null }, - $or: [ - { cover: { $exists: false } }, - { cover: null } - ] - }, { fields: { _id: 1 }, limit: 1 }); - case 'use-css-class-for-boards-colors': - // Check if any board uses old color system (non-CSS class) - return !!Boards.findOne({ - color: { $exists: true, $ne: null }, - colorClass: { $exists: false } - }, { fields: { _id: 1 }, limit: 1 }); - case 'denormalize-star-number-per-board': - return !!Boards.findOne({ - $or: [ - { stars: { $exists: false } }, - { stars: null } - ] - }, { fields: { _id: 1 }, limit: 1 }); - case 'add-member-isactive-field': - return !!Boards.findOne({ - members: { $elemMatch: { isActive: { $exists: false } } } - }, { fields: { _id: 1 }, limit: 1 }); - case 'ensure-valid-swimlane-ids': - // Check for cards without swimlaneId (needs validation) - return !!Cards.findOne({ - $or: [ - { swimlaneId: { $exists: false } }, - { swimlaneId: null }, - { swimlaneId: '' } - ] - }, { fields: { _id: 1 }, limit: 1 }); - case 'add-swimlanes': - // Only needed if we have cards without swimlaneId (same as ensure-valid-swimlane-ids) - return !!Cards.findOne({ - $or: [ - { swimlaneId: { $exists: false } }, - { swimlaneId: null }, - { swimlaneId: '' } - ] - }, { fields: { _id: 1 }, limit: 1 }); - case 'add-checklist-items': - // Check if checklists exist but items are not properly set up - return !!Checklists.findOne({ - $or: [ - { items: { $exists: false } }, - { items: null } - ] - }, { fields: { _id: 1 }, limit: 1 }); - case 'add-card-types': - return !!Cards.findOne({ - $or: [ - { type: { $exists: false } }, - { type: null }, - { type: '' } - ] - }, { fields: { _id: 1 }, limit: 1 }); - case 'migrate-attachments-collectionFS-to-ostrioFiles': - // In fresh WeKan installations (Meteor-Files only), no CollectionFS migration needed - return false; - case 'migrate-avatars-collectionFS-to-ostrioFiles': - // In fresh WeKan installations (Meteor-Files only), no CollectionFS migration needed - return false; - case 'migrate-lists-to-per-swimlane': { - const boards = Boards.find({}, { fields: { _id: 1 }, limit: 100 }).fetch(); - return boards.some(board => comprehensiveBoardMigration.needsMigration(board._id)); - } - default: - return false; // Changed from true to false - only run migrations we explicitly check for - } - } - /** * Execute a migration step */ async executeMigrationStep(jobId, stepIndex, stepData, stepId) { const { name, duration } = stepData; - - // Check if this is the star count migration that needs real implementation - if (stepId === 'denormalize-star-number-per-board') { - await this.executeDenormalizeStarCount(jobId, stepIndex, stepData); - return; - } - - // Check if this is the swimlane validation migration - if (stepId === 'ensure-valid-swimlane-ids') { - await this.executeEnsureValidSwimlaneIds(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'migrate-lists-to-per-swimlane') { - await this.executeComprehensiveBoardMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'lowercase-board-permission') { - await this.executeLowercasePermission(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'change-attachments-type-for-non-images') { - await this.executeAttachmentTypeStandardization(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'card-covers') { - await this.executeCardCoversMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'add-member-isactive-field') { - await this.executeMemberActivityMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'add-swimlanes') { - await this.executeAddSwimlanesIdMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'add-card-types') { - await this.executeAddCardTypesMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'migrate-attachments-collectionFS-to-ostrioFiles') { - await this.executeAttachmentMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'migrate-avatars-collectionFS-to-ostrioFiles') { - await this.executeAvatarMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'use-css-class-for-boards-colors') { - await this.executeBoardColorMigration(jobId, stepIndex, stepData); - return; - } - - if (stepId === 'add-checklist-items') { - await this.executeChecklistItemsMigration(jobId, stepIndex, stepData); - return; - } - - // Unknown migration step - log and mark as complete without doing anything - console.warn(`Unknown migration step: ${stepId} - no handler found. Marking as complete without execution.`); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration skipped: No handler for ${stepId}` - }); - } - - /** - * Execute the denormalize star count migration - */ - async executeDenormalizeStarCount(jobId, stepIndex, stepData) { - try { - const { name } = stepData; - - // Update progress: Starting + + // Simulate step execution with progress updates for other migrations + const progressSteps = 10; + for (let i = 0; i <= progressSteps; i++) { + const progress = Math.round((i / progressSteps) * 100); + + // Update step progress cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Counting starred boards across all users...' + progress, + currentAction: `Executing: ${name} (${progress}%)` }); - - // Build a map of boardId -> star count - const starCounts = new Map(); - - // Get all users with starred boards - const users = Users.find( - { 'profile.starredBoards': { $exists: true, $ne: [] } }, - { fields: { 'profile.starredBoards': 1 } } - ).fetch(); - - // Update progress: Counting - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 20, - currentAction: `Analyzing ${users.length} users with starred boards...` - }); - - // Count stars for each board - users.forEach(user => { - const starredBoards = (user.profile && user.profile.starredBoards) || []; - starredBoards.forEach(boardId => { - starCounts.set(boardId, (starCounts.get(boardId) || 0) + 1); - }); - }); - - // Update progress: Updating boards - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 50, - currentAction: `Updating star counts for ${starCounts.size} boards...` - }); - - // Update all boards with their star counts - let updatedCount = 0; - const totalBoards = starCounts.size; - - for (const [boardId, count] of starCounts.entries()) { - try { - Boards.update(boardId, { $set: { stars: count } }); - updatedCount++; - - // Update progress periodically - if (updatedCount % 10 === 0 || updatedCount === totalBoards) { - const progress = 50 + Math.round((updatedCount / totalBoards) * 40); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress, - currentAction: `Updated ${updatedCount}/${totalBoards} boards...` - }); - } - } catch (error) { - console.error(`Failed to update star count for board ${boardId}:`, error); - // Store error in database - cronJobStorage.saveJobError(jobId, { - stepId: 'denormalize-star-number-per-board', - stepIndex, - error, - severity: 'warning', - context: { boardId, operation: 'update_star_count' } - }); - } - } - - // Also set stars to 0 for boards that have no stars - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 90, - currentAction: 'Initializing boards with no stars...' - }); - - const boardsWithoutStars = Boards.find( - { - $or: [ - { stars: { $exists: false } }, - { stars: null } - ] - }, - { fields: { _id: 1 } } - ).fetch(); - - boardsWithoutStars.forEach(board => { - // Only set to 0 if not already counted - if (!starCounts.has(board._id)) { - try { - Boards.update(board._id, { $set: { stars: 0 } }); - } catch (error) { - console.error(`Failed to initialize star count for board ${board._id}:`, error); - // Store error in database - cronJobStorage.saveJobError(jobId, { - stepId: 'denormalize-star-number-per-board', - stepIndex, - error, - severity: 'warning', - context: { boardId: board._id, operation: 'initialize_star_count' } - }); - } - } - }); - - // Complete - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration complete: Updated ${updatedCount} boards with star counts` - }); - - console.log(`Star count migration completed: ${updatedCount} boards updated, ${boardsWithoutStars.length} initialized to 0`); - - } catch (error) { - console.error('Error executing denormalize star count migration:', error); - // Store error in database - cronJobStorage.saveJobError(jobId, { - stepId: 'denormalize-star-number-per-board', - stepIndex, - error, - severity: 'error', - context: { operation: 'denormalize_star_count_migration' } - }); - throw error; + + // Simulate work + await new Promise(resolve => setTimeout(resolve, duration / progressSteps)); } } - /** - * Execute the ensure valid swimlane IDs migration - */ - async executeEnsureValidSwimlaneIds(jobId, stepIndex, stepData) { - try { - const { name } = stepData; - - // Update progress: Starting - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Starting swimlane ID validation...' - }); - - // Run the migration function - const result = await runEnsureValidSwimlaneIdsMigration(); - - // Update progress: Complete - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration complete: Fixed ${result.cardsFixed || 0} cards, ${result.listsFixed || 0} lists, rescued ${result.cardsRescued || 0} orphaned cards` - }); - - console.log(`Swimlane ID validation migration completed:`, result); - - } catch (error) { - console.error('Error executing swimlane ID validation migration:', error); - // Store error in database - cronJobStorage.saveJobError(jobId, { - stepId: 'ensure-valid-swimlane-ids', - stepIndex, - error, - severity: 'error', - context: { operation: 'ensure_valid_swimlane_ids_migration' } - }); - throw error; - } - } - - /** - * Execute the lowercase board permission migration - */ - async executeLowercasePermission(jobId, stepIndex, stepData) { - try { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Searching for boards with uppercase permissions...' - }); - - // Find boards with uppercase permission values - const boards = Boards.find({ - $or: [ - { permission: 'PUBLIC' }, - { permission: 'Private' }, - { permission: 'PRIVATE' } - ] - }).fetch(); - - if (boards.length === 0) { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: 'No boards need permission conversion.' - }); - return; - } - - let updatedCount = 0; - const totalBoards = boards.length; - - for (const board of boards) { - try { - const newPermission = board.permission.toLowerCase(); - Boards.update(board._id, { $set: { permission: newPermission } }); - updatedCount++; - - // Update progress - const progress = Math.round((updatedCount / totalBoards) * 100); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress, - currentAction: `Converting permissions: ${updatedCount}/${totalBoards} boards updated` - }); - } catch (error) { - console.error(`Failed to update permission for board ${board._id}:`, error); - cronJobStorage.saveJobError(jobId, { - stepId: 'lowercase-board-permission', - stepIndex, - error, - severity: 'warning', - context: { boardId: board._id, oldPermission: board.permission } - }); - } - } - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration complete: Converted ${updatedCount} board permissions to lowercase` - }); - - console.log(`Lowercase permission migration completed: ${updatedCount} boards updated`); - - } catch (error) { - console.error('Error executing lowercase permission migration:', error); - cronJobStorage.saveJobError(jobId, { - stepId: 'lowercase-board-permission', - stepIndex, - error, - severity: 'error', - context: { operation: 'lowercase_permission_migration' } - }); - throw error; - } - } - - /** - * Execute the comprehensive per-swimlane list migration across boards - */ - async executeComprehensiveBoardMigration(jobId, stepIndex, stepData) { - try { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Calculating amount of changes to do' - }); - - const boards = Boards.find({}, { fields: { _id: 1, title: 1 } }).fetch(); - const boardsToMigrate = boards.filter(board => comprehensiveBoardMigration.needsMigration(board._id)); - - if (boardsToMigrate.length === 0) { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: 'No boards need per-swimlane migration.' - }); - return; - } - - let completed = 0; - - for (const board of boardsToMigrate) { - const boardLabel = board.title ? `"${board.title}"` : board._id; - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: Math.round((completed / boardsToMigrate.length) * 100), - currentAction: `Migrating board ${completed + 1}/${boardsToMigrate.length}: ${boardLabel}` - }); - - try { - await comprehensiveBoardMigration.executeMigration(board._id, (progressData) => { - if (!progressData) return; - - const boardProgress = progressData.overallProgress || 0; - const overallProgress = Math.round( - ((completed + (boardProgress / 100)) / boardsToMigrate.length) * 100 - ); - - const stepLabel = progressData.stepName || progressData.stepStatus || 'Working'; - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: overallProgress, - currentAction: `Migrating board ${completed + 1}/${boardsToMigrate.length}: ${boardLabel} - ${stepLabel}` - }); - }); - } catch (error) { - cronJobStorage.saveJobError(jobId, { - stepId: 'migrate-lists-to-per-swimlane', - stepIndex, - error, - severity: 'error', - context: { boardId: board._id, boardTitle: board.title || '' } - }); - } - - completed++; - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: Math.round((completed / boardsToMigrate.length) * 100), - currentAction: `Completed ${completed}/${boardsToMigrate.length} boards` - }); - } - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Per-swimlane migration finished: ${completed}/${boardsToMigrate.length} boards processed` - }); - - } catch (error) { - console.error('Error executing per-swimlane list migration:', error); - cronJobStorage.saveJobError(jobId, { - stepId: 'migrate-lists-to-per-swimlane', - stepIndex, - error, - severity: 'error', - context: { operation: 'comprehensive_board_migration' } - }); - throw error; - } - } - - /** - * Execute attachment type standardization migration - */ - async executeAttachmentTypeStandardization(jobId, stepIndex, stepData) { - try { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Searching for attachments without proper type...' - }); - - const attachments = Attachments.find({ - $or: [ - { type: { $exists: false } }, - { type: null }, - { type: '' } - ] - }).fetch(); - - if (attachments.length === 0) { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: 'No attachments need type updates.' - }); - return; - } - - let updatedCount = 0; - const totalAttachments = attachments.length; - - for (const attachment of attachments) { - try { - // Set type to 'application/octet-stream' for non-images - const type = attachment.type || 'application/octet-stream'; - Attachments.update(attachment._id, { $set: { type } }); - updatedCount++; - - if (updatedCount % 10 === 0 || updatedCount === totalAttachments) { - const progress = Math.round((updatedCount / totalAttachments) * 100); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress, - currentAction: `Updating attachment types: ${updatedCount}/${totalAttachments}` - }); - } - } catch (error) { - console.error(`Failed to update attachment ${attachment._id}:`, error); - cronJobStorage.saveJobError(jobId, { - stepId: 'change-attachments-type-for-non-images', - stepIndex, - error, - severity: 'warning', - context: { attachmentId: attachment._id } - }); - } - } - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration complete: Updated ${updatedCount} attachments` - }); - - } catch (error) { - console.error('Error executing attachment type migration:', error); - cronJobStorage.saveJobError(jobId, { - stepId: 'change-attachments-type-for-non-images', - stepIndex, - error, - severity: 'error', - context: { operation: 'attachment_type_migration' } - }); - throw error; - } - } - - /** - * Execute card covers migration - */ - async executeCardCoversMigration(jobId, stepIndex, stepData) { - try { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Searching for cards with old cover format...' - }); - - const cards = Cards.find({ coverId: { $exists: true, $ne: null } }).fetch(); - - if (cards.length === 0) { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: 'No cards need cover migration.' - }); - return; - } - - let updatedCount = 0; - const totalCards = cards.length; - - for (const card of cards) { - try { - // Denormalize cover data if needed - if (!card.cover && card.coverId) { - const attachment = Attachments.findOne(card.coverId); - if (attachment) { - Cards.update(card._id, { - $set: { - cover: { - _id: attachment._id, - url: attachment.url(), - type: attachment.type - } - } - }); - updatedCount++; - } - } - - if (updatedCount % 10 === 0 || updatedCount === totalCards) { - const progress = Math.round(((updatedCount + (totalCards - updatedCount) * 0.1) / totalCards) * 100); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress, - currentAction: `Migrating card covers: ${updatedCount}/${totalCards}` - }); - } - } catch (error) { - console.error(`Failed to update card cover ${card._id}:`, error); - cronJobStorage.saveJobError(jobId, { - stepId: 'card-covers', - stepIndex, - error, - severity: 'warning', - context: { cardId: card._id } - }); - } - } - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration complete: Updated ${updatedCount} card covers` - }); - - } catch (error) { - console.error('Error executing card covers migration:', error); - cronJobStorage.saveJobError(jobId, { - stepId: 'card-covers', - stepIndex, - error, - severity: 'error', - context: { operation: 'card_covers_migration' } - }); - throw error; - } - } - - /** - * Execute member activity status migration - */ - async executeMemberActivityMigration(jobId, stepIndex, stepData) { - try { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Searching for boards without member isActive field...' - }); - - const boards = Boards.find({}).fetch(); - let totalMembers = 0; - let updatedMembers = 0; - - for (const board of boards) { - if (board.members && board.members.length > 0) { - totalMembers += board.members.length; - } - } - - if (totalMembers === 0) { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: 'No board members to update.' - }); - return; - } - - for (const board of boards) { - if (!board.members || board.members.length === 0) continue; - - const updatedMembers_board = board.members.map(member => { - if (member.isActive === undefined) { - return { ...member, isActive: true }; - } - return member; - }); - - try { - Boards.update(board._id, { $set: { members: updatedMembers_board } }); - updatedMembers += board.members.length; - - const progress = Math.round((updatedMembers / totalMembers) * 100); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress, - currentAction: `Updating member status: ${updatedMembers}/${totalMembers}` - }); - } catch (error) { - console.error(`Failed to update members for board ${board._id}:`, error); - cronJobStorage.saveJobError(jobId, { - stepId: 'add-member-isactive-field', - stepIndex, - error, - severity: 'warning', - context: { boardId: board._id } - }); - } - } - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration complete: Updated ${updatedMembers} board members` - }); - - } catch (error) { - console.error('Error executing member activity migration:', error); - cronJobStorage.saveJobError(jobId, { - stepId: 'add-member-isactive-field', - stepIndex, - error, - severity: 'error', - context: { operation: 'member_activity_migration' } - }); - throw error; - } - } - - /** - * Execute add swimlane IDs to cards migration - */ - async executeAddSwimlanesIdMigration(jobId, stepIndex, stepData) { - try { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Searching for cards without swimlaneId...' - }); - - const boards = Boards.find({}).fetch(); - let totalCards = 0; - let updatedCards = 0; - - for (const board of boards) { - const defaultSwimlane = Swimlanes.findOne({ boardId: board._id, type: 'swimlane', title: 'Default' }); - const swimlaneId = defaultSwimlane ? defaultSwimlane._id : null; - - if (!swimlaneId) continue; - - const cards = Cards.find({ - boardId: board._id, - $or: [ - { swimlaneId: { $exists: false } }, - { swimlaneId: null }, - { swimlaneId: '' } - ] - }).fetch(); - - totalCards += cards.length; - - for (const card of cards) { - try { - Cards.update(card._id, { $set: { swimlaneId } }); - updatedCards++; - - if (updatedCards % 10 === 0) { - const progress = Math.round((updatedCards / Math.max(totalCards, 1)) * 100); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress, - currentAction: `Adding swimlaneId to cards: ${updatedCards}/${totalCards}` - }); - } - } catch (error) { - console.error(`Failed to update card ${card._id}:`, error); - cronJobStorage.saveJobError(jobId, { - stepId: 'add-swimlanes', - stepIndex, - error, - severity: 'warning', - context: { cardId: card._id, boardId: board._id } - }); - } - } - } - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration complete: Updated ${updatedCards} cards with swimlaneId` - }); - - } catch (error) { - console.error('Error executing add swimlanes migration:', error); - cronJobStorage.saveJobError(jobId, { - stepId: 'add-swimlanes', - stepIndex, - error, - severity: 'error', - context: { operation: 'add_swimlanes_migration' } - }); - throw error; - } - } - - /** - * Execute add card types migration - */ - async executeAddCardTypesMigration(jobId, stepIndex, stepData) { - try { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Searching for cards without type field...' - }); - - const cards = Cards.find({ - $or: [ - { type: { $exists: false } }, - { type: null }, - { type: '' } - ] - }).fetch(); - - if (cards.length === 0) { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: 'No cards need type field.' - }); - return; - } - - let updatedCards = 0; - const totalCards = cards.length; - - for (const card of cards) { - try { - // Determine card type based on linked card/board - let cardType = 'cardType-card'; // default - if (card.linkedId) { - cardType = card.linkedId.startsWith('board-') ? 'cardType-linkedBoard' : 'cardType-linkedCard'; - } - - Cards.update(card._id, { $set: { type: cardType } }); - updatedCards++; - - if (updatedCards % 10 === 0 || updatedCards === totalCards) { - const progress = Math.round((updatedCards / totalCards) * 100); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress, - currentAction: `Adding type to cards: ${updatedCards}/${totalCards}` - }); - } - } catch (error) { - console.error(`Failed to update card ${card._id}:`, error); - cronJobStorage.saveJobError(jobId, { - stepId: 'add-card-types', - stepIndex, - error, - severity: 'warning', - context: { cardId: card._id } - }); - } - } - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration complete: Updated ${updatedCards} cards with type field` - }); - - } catch (error) { - console.error('Error executing add card types migration:', error); - cronJobStorage.saveJobError(jobId, { - stepId: 'add-card-types', - stepIndex, - error, - severity: 'error', - context: { operation: 'add_card_types_migration' } - }); - throw error; - } - } - - /** - * Execute attachment migration from CollectionFS to Meteor-Files - * In fresh WeKan installations, this migration is not needed as they use Meteor-Files only - */ - async executeAttachmentMigration(jobId, stepIndex, stepData) { - try { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Checking for legacy CollectionFS attachments...' - }); - - const totalAttachments = Attachments.find().count(); - - // Check if any attachments need migration (old structure without proper meta) - const needsMigration = Attachments.findOne({ - $or: [ - { 'meta.boardId': { $exists: false } }, - { 'meta.listId': { $exists: false } }, - { 'meta.cardId': { $exists: false } } - ] - }); - - if (!needsMigration) { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `All ${totalAttachments} attachments are already in Meteor-Files format. No migration needed.` - }); - console.log(`CollectionFS migration: No legacy attachments found (${totalAttachments} total attachments all in modern format).`); - return; - } - - // If we reach here, there are attachments to migrate (rare in fresh installs) - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 50, - currentAction: `Migrating ${totalAttachments} attachments from CollectionFS to Meteor-Files...` - }); - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration complete: Verified ${totalAttachments} attachments are in correct format.` - }); - - console.log(`Completed CollectionFS migration: ${totalAttachments} attachments verified.`); - - } catch (error) { - console.error('Error executing attachment migration:', error); - cronJobStorage.saveJobError(jobId, { - stepId: 'migrate-attachments-collectionFS-to-ostrioFiles', - stepIndex, - error, - severity: 'error', - context: { operation: 'attachment_migration' } - }); - throw error; - } - } - - /** - * Execute avatar migration from CollectionFS to Meteor-Files - */ - async executeAvatarMigration(jobId, stepIndex, stepData) { - try { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Checking for legacy CollectionFS avatars...' - }); - - // In fresh installations, avatars are already in Meteor-Files format - // No action needed - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: 'All avatars are in Meteor-Files format. No migration needed.' - }); - console.log('Avatar migration: No legacy avatars found. Installation appears to be fresh.'); - - } catch (error) { - console.error('Error executing avatar migration:', error); - cronJobStorage.saveJobError(jobId, { - stepId: 'migrate-avatars-collectionFS-to-ostrioFiles', - stepIndex, - error, - severity: 'error', - context: { operation: 'avatar_migration' } - }); - throw error; - } - } - - /** - * Execute board color CSS class migration - */ - async executeBoardColorMigration(jobId, stepIndex, stepData) { - try { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Checking board colors...' - }); - - const boardsNeedingMigration = Boards.find({ - color: { $exists: true, $ne: null }, - colorClass: { $exists: false } - }, { fields: { _id: 1 } }).fetch(); - - if (boardsNeedingMigration.length === 0) { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: 'All boards already use CSS color classes. No migration needed.' - }); - return; - } - - let updated = 0; - const total = boardsNeedingMigration.length; - - for (const board of boardsNeedingMigration) { - // Color to colorClass mapping (simplified - actual colors handled by templates) - const colorClass = 'wekan-' + (board.color || 'blue'); - Boards.update(board._id, { $set: { colorClass } }); - updated++; - - const progress = Math.round((updated / total) * 100); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress, - currentAction: `Migrating board colors: ${updated}/${total}` - }); - } - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration complete: Updated ${updated} board colors to CSS classes` - }); - - } catch (error) { - console.error('Error executing board color migration:', error); - cronJobStorage.saveJobError(jobId, { - stepId: 'use-css-class-for-boards-colors', - stepIndex, - error, - severity: 'error', - context: { operation: 'board_color_migration' } - }); - throw error; - } - } - - /** - * Execute checklist items migration - */ - async executeChecklistItemsMigration(jobId, stepIndex, stepData) { - try { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 0, - currentAction: 'Checking checklists...' - }); - - const checklistsNeedingMigration = Checklists.find({ - $or: [ - { items: { $exists: false } }, - { items: null } - ] - }, { fields: { _id: 1 } }).fetch(); - - if (checklistsNeedingMigration.length === 0) { - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: 'All checklists properly configured. No migration needed.' - }); - return; - } - - let updated = 0; - const total = checklistsNeedingMigration.length; - - for (const checklist of checklistsNeedingMigration) { - Checklists.update(checklist._id, { $set: { items: [] } }); - updated++; - - const progress = Math.round((updated / total) * 100); - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress, - currentAction: `Initializing checklists: ${updated}/${total}` - }); - } - - cronJobStorage.saveJobStep(jobId, stepIndex, { - progress: 100, - currentAction: `Migration complete: Initialized ${updated} checklists` - }); - - } catch (error) { - console.error('Error executing checklist items migration:', error); - cronJobStorage.saveJobError(jobId, { - stepId: 'add-checklist-items', - stepIndex, - error, - severity: 'error', - context: { operation: 'checklist_items_migration' } - }); - throw error; - } - } /** * Execute a board operation job */ async executeBoardOperationJob(jobId, jobData) { const { operationType, operationData } = jobData; - + // Use existing board operation logic await this.executeBoardOperation(jobId, operationType, operationData); } @@ -1490,16 +478,16 @@ class CronMigrationManager { */ async executeBoardMigrationJob(jobId, jobData) { const { boardId, boardTitle, migrationType } = jobData; - + try { // Starting board migration - + // Create migration steps for this board const steps = this.createBoardMigrationSteps(boardId, migrationType); - + for (let i = 0; i < steps.length; i++) { const stepData = steps[i]; - + // Save step status cronJobStorage.saveJobStep(jobId, i, { stepName: stepData.name, @@ -1525,7 +513,7 @@ class CronMigrationManager { // Mark board as migrated this.markBoardAsMigrated(boardId, migrationType); - + // Completed board migration } catch (error) { @@ -1539,7 +527,7 @@ class CronMigrationManager { */ createBoardMigrationSteps(boardId, migrationType) { const steps = []; - + if (migrationType === 'full_board_migration') { steps.push( { name: 'Check board structure', duration: 500, type: 'validation' }, @@ -1556,7 +544,7 @@ class CronMigrationManager { { name: 'Finalize changes', duration: 1000, type: 'finalize' } ); } - + return steps; } @@ -1565,18 +553,18 @@ class CronMigrationManager { */ async executeBoardMigrationStep(jobId, stepIndex, stepData, boardId) { const { name, duration, type } = stepData; - + // Simulate step execution with progress updates const progressSteps = 10; for (let i = 0; i <= progressSteps; i++) { const progress = Math.round((i / progressSteps) * 100); - + // Update step progress cronJobStorage.saveJobStep(jobId, stepIndex, { progress, currentAction: `Executing: ${name} (${progress}%)` }); - + // Simulate work based on step type await this.simulateBoardMigrationWork(type, duration / progressSteps); } @@ -1646,8 +634,8 @@ class CronMigrationManager { SyncedCron.add({ name: step.cronName, schedule: (parser) => parser.text(step.schedule), - job: async () => { - await this.runMigrationStep(step); + job: () => { + this.runMigrationStep(step); }, }); } @@ -1657,13 +645,8 @@ class CronMigrationManager { */ async runMigrationStep(step) { try { - // Check if already completed - if (step.completed) { - return; // Skip if already completed - } - // Starting migration step - + cronMigrationCurrentStep.set(step.name); cronMigrationStatus.set(`Running: ${step.description}`); cronIsMigrating.set(true); @@ -1673,7 +656,7 @@ class CronMigrationManager { for (let i = 0; i <= progressSteps; i++) { step.progress = (i / progressSteps) * 100; this.updateProgress(); - + // Simulate work await new Promise(resolve => setTimeout(resolve, 100)); } @@ -1683,11 +666,8 @@ class CronMigrationManager { step.progress = 100; step.status = 'completed'; - // Remove the cron job to prevent re-running every minute - SyncedCron.remove(step.cronName); - // Completed migration step - + // Update progress this.updateProgress(); @@ -1708,54 +688,18 @@ class CronMigrationManager { this.isRunning = true; cronIsMigrating.set(true); - cronMigrationStatus.set('Starting...'); - cronMigrationProgress.set(0); - cronMigrationCurrentStepNum.set(0); - cronMigrationTotalSteps.set(0); + cronMigrationStatus.set('Adding migrations to job queue...'); this.startTime = Date.now(); - // Update CronJobStatus for immediate pub/sub notification - CronJobStatus.upsert( - { jobId: 'migration' }, - { - $set: { - jobId: 'migration', - status: 'starting', - statusMessage: 'Starting migrations...', - progress: 0, - updatedAt: new Date() - } - } - ); - try { - // Remove cron jobs to prevent conflicts with job queue - this.migrationSteps.forEach(step => { - try { - SyncedCron.remove(step.cronName); - } catch (error) { - // Ignore errors if cron job doesn't exist - } - }); - - let queuedJobs = 0; - // Add all migration steps to the job queue for (let i = 0; i < this.migrationSteps.length; i++) { const step = this.migrationSteps[i]; - + if (step.completed) { continue; // Skip already completed steps } - if (!this.isMigrationNeeded(step.id)) { - step.completed = true; - step.progress = 100; - step.status = 'completed'; - this.updateProgress(); - continue; - } - // Add to job queue const jobId = `migration_${step.id}_${Date.now()}`; cronJobStorage.addToQueue(jobId, 'migration', step.weight, { @@ -1763,7 +707,6 @@ class CronMigrationManager { stepName: step.name, stepDescription: step.description }); - queuedJobs++; // Save initial job status cronJobStorage.saveJobStatus(jobId, { @@ -1776,47 +719,8 @@ class CronMigrationManager { }); } - if (queuedJobs === 0) { - cronIsMigrating.set(false); - cronMigrationStatus.set('No migration needed'); - cronMigrationProgress.set(0); - cronMigrationCurrentStep.set(''); - cronMigrationCurrentStepNum.set(0); - cronMigrationTotalSteps.set(0); - this.isRunning = false; - - // Update CronJobStatus - CronJobStatus.upsert( - { jobId: 'migration' }, - { - $set: { - jobId: 'migration', - status: 'idle', - statusMessage: 'No migration needed', - progress: 0, - updatedAt: new Date() - } - } - ); - return; - } - - // Update to running state - CronJobStatus.upsert( - { jobId: 'migration' }, - { - $set: { - jobId: 'migration', - status: 'running', - statusMessage: 'Running migrations...', - progress: 0, - updatedAt: new Date() - } - } - ); - - // Status will be updated by monitorMigrationProgress - + cronMigrationStatus.set('Migrations added to queue. Processing will begin shortly...'); + // Start monitoring progress this.monitorMigrationProgress(); @@ -1828,135 +732,45 @@ class CronMigrationManager { } } - /** - * Start a specific migration by index - */ - async startSpecificMigration(migrationIndex) { - if (this.isRunning) { - return; - } - - const step = this.migrationSteps[migrationIndex]; - if (!step) { - throw new Meteor.Error('invalid-migration', 'Migration not found'); - } - - if (!this.isMigrationNeeded(step.id)) { - step.completed = true; - step.progress = 100; - step.status = 'completed'; - this.updateProgress(); - cronIsMigrating.set(false); - cronMigrationStatus.set('No migration needed'); - this.isRunning = false; - return { skipped: true }; - } - - this.isRunning = true; - cronIsMigrating.set(true); - cronMigrationStatus.set('Starting...'); - cronMigrationProgress.set(0); - cronMigrationCurrentStepNum.set(1); - cronMigrationTotalSteps.set(1); - this.startTime = Date.now(); - - try { - // Remove cron job to prevent conflicts - try { - SyncedCron.remove(step.cronName); - } catch (error) { - // Ignore errors if cron job doesn't exist - } - - // Add single migration step to the job queue - const jobId = `migration_${step.id}_${Date.now()}`; - cronJobStorage.addToQueue(jobId, 'migration', step.weight, { - stepId: step.id, - stepName: step.name, - stepDescription: step.description - }); - - // Save initial job status - cronJobStorage.saveJobStatus(jobId, { - jobType: 'migration', - status: 'pending', - progress: 0, - stepId: step.id, - stepName: step.name, - stepDescription: step.description - }); - - // Status will be updated by monitorMigrationProgress - - // Start monitoring progress - this.monitorMigrationProgress(); - - } catch (error) { - console.error('Failed to start migration:', error); - cronMigrationStatus.set(`Failed to start migration: ${error.message}`); - cronIsMigrating.set(false); - this.isRunning = false; - } - } - /** * Monitor migration progress */ monitorMigrationProgress() { - // Clear any existing monitor interval - if (this.monitorInterval) { - Meteor.clearInterval(this.monitorInterval); - } - - this.monitorInterval = Meteor.setInterval(() => { + const monitorInterval = Meteor.setInterval(() => { const stats = cronJobStorage.getQueueStats(); const incompleteJobs = cronJobStorage.getIncompleteJobs(); - const pausedJobs = incompleteJobs.filter(job => job.status === 'paused'); - - // Check if all migrations are completed first + + // Update progress const totalJobs = stats.total; const completedJobs = stats.completed; - - if (stats.completed === totalJobs && totalJobs > 0 && stats.running === 0) { - // All migrations completed - immediately clear isMigrating to hide progress - cronIsMigrating.set(false); - cronMigrationStatus.set('All migrations completed successfully!'); - cronMigrationProgress.set(0); - cronMigrationCurrentStep.set(''); - cronMigrationCurrentStepNum.set(0); - cronMigrationTotalSteps.set(0); - - // Clear status message after delay - setTimeout(() => { - cronMigrationStatus.set(''); - }, 5000); - - Meteor.clearInterval(this.monitorInterval); - this.monitorInterval = null; - return; // Exit early to avoid setting progress to 100% - } - - // Update progress for active migrations const progress = totalJobs > 0 ? Math.round((completedJobs / totalJobs) * 100) : 0; + cronMigrationProgress.set(progress); - cronMigrationTotalSteps.set(totalJobs); - const currentStepNum = completedJobs + (stats.running > 0 ? 1 : 0); - cronMigrationCurrentStepNum.set(currentStepNum); - + // Update status if (stats.running > 0) { const runningJob = incompleteJobs.find(job => job.status === 'running'); if (runningJob) { - cronMigrationStatus.set(`Running: ${currentStepNum}/${totalJobs} ${runningJob.stepName || 'Migration in progress'}`); - cronMigrationCurrentStep.set(''); + cronMigrationCurrentStep.set(runningJob.stepName || 'Processing migration...'); + cronMigrationStatus.set(`Running: ${runningJob.stepName || 'Migration in progress'}`); } - } else if (pausedJobs.length > 0) { - cronIsMigrating.set(false); - cronMigrationStatus.set(`Migrations paused (${pausedJobs.length})`); - cronMigrationCurrentStep.set(''); } else if (stats.pending > 0) { cronMigrationStatus.set(`${stats.pending} migrations pending in queue`); + cronMigrationCurrentStep.set('Waiting for available resources...'); + } else if (stats.completed === totalJobs && totalJobs > 0) { + // All migrations completed + cronMigrationStatus.set('All migrations completed successfully!'); + cronMigrationProgress.set(100); cronMigrationCurrentStep.set(''); + + // Clear status after delay + setTimeout(() => { + cronIsMigrating.set(false); + cronMigrationStatus.set(''); + cronMigrationProgress.set(0); + }, 3000); + + Meteor.clearInterval(monitorInterval); } }, 2000); // Check every 2 seconds } @@ -1966,7 +780,7 @@ class CronMigrationManager { */ async startCronJob(cronName) { // Change schedule to run once - const job = SyncedCron._entries?.[cronName]; + const job = SyncedCron.jobs.find(j => j.name === cronName); if (job) { job.schedule = 'once'; SyncedCron.start(); @@ -2001,20 +815,9 @@ class CronMigrationManager { /** * Pause a specific cron job - * Note: quave:synced-cron only has global pause(), so we implement per-job pause - * by storing the job config and removing it, then re-adding on resume. */ pauseCronJob(cronName) { - const entry = SyncedCron._entries?.[cronName]; - if (entry) { - // Store the job config before removing - this.pausedJobs.set(cronName, { - name: entry.name, - schedule: entry.schedule, - job: entry.job - }); - SyncedCron.remove(cronName); - } + SyncedCron.pause(cronName); const step = this.migrationSteps.find(s => s.cronName === cronName); if (step) { step.status = 'paused'; @@ -2024,15 +827,9 @@ class CronMigrationManager { /** * Resume a specific cron job - * Note: quave:synced-cron doesn't have resume(), so we re-add the stored job config. */ resumeCronJob(cronName) { - const pausedJob = this.pausedJobs.get(cronName); - if (pausedJob) { - SyncedCron.add(pausedJob); - this.pausedJobs.delete(cronName); - SyncedCron.start(); - } + SyncedCron.resume(cronName); const step = this.migrationSteps.find(s => s.cronName === cronName); if (step) { step.status = 'running'; @@ -2079,7 +876,7 @@ class CronMigrationManager { return total + (step.completed ? step.weight : step.progress * step.weight / 100); }, 0); const progress = Math.round((completedWeight / totalWeight) * 100); - + cronMigrationProgress.set(progress); cronMigrationSteps.set([...this.migrationSteps]); } @@ -2088,15 +885,14 @@ class CronMigrationManager { * Update cron jobs list */ updateCronJobsList() { - // Check if SyncedCron is available and has entries - const entries = SyncedCron?._entries; - if (!entries || typeof entries !== 'object') { + // Check if SyncedCron is available and has jobs + if (!SyncedCron || !SyncedCron.jobs || !Array.isArray(SyncedCron.jobs)) { // SyncedCron not available or no jobs yet cronJobs.set([]); return; } - const jobs = Object.values(entries).map(job => { + const jobs = SyncedCron.jobs.map(job => { const step = this.migrationSteps.find(s => s.cronName === job.name); return { name: job.name, @@ -2129,7 +925,7 @@ class CronMigrationManager { */ startBoardOperation(boardId, operationType, operationData) { const operationId = `${boardId}_${operationType}_${Date.now()}`; - + // Add to job queue cronJobStorage.addToQueue(operationId, 'board_operation', 3, { boardId, @@ -2174,7 +970,7 @@ class CronMigrationManager { async executeBoardOperation(operationId, operationType, operationData) { const operations = boardOperations.get(); const operation = operations.get(operationId); - + if (!operation) { console.error(`Operation ${operationId} not found`); return; @@ -2182,7 +978,7 @@ class CronMigrationManager { try { console.log(`Starting board operation: ${operationType} for board ${operation.boardId}`); - + // Update operation status operation.status = 'running'; operation.progress = 0; @@ -2265,13 +1061,13 @@ class CronMigrationManager { async copyBoard(operationId, data) { const { sourceBoardId, targetBoardId, copyOptions } = data; const operation = boardOperations.get().get(operationId); - + // Simulate copy progress const steps = ['copying_swimlanes', 'copying_lists', 'copying_cards', 'copying_attachments', 'finalizing']; for (let i = 0; i < steps.length; i++) { operation.progress = Math.round(((i + 1) / steps.length) * 100); this.updateBoardOperation(operationId, operation); - + // Simulate work await new Promise(resolve => setTimeout(resolve, 1000)); } @@ -2283,13 +1079,13 @@ class CronMigrationManager { async moveBoard(operationId, data) { const { sourceBoardId, targetBoardId, moveOptions } = data; const operation = boardOperations.get().get(operationId); - + // Simulate move progress const steps = ['preparing_move', 'moving_swimlanes', 'moving_lists', 'moving_cards', 'updating_references', 'finalizing']; for (let i = 0; i < steps.length; i++) { operation.progress = Math.round(((i + 1) / steps.length) * 100); this.updateBoardOperation(operationId, operation); - + // Simulate work await new Promise(resolve => setTimeout(resolve, 800)); } @@ -2301,13 +1097,13 @@ class CronMigrationManager { async copySwimlane(operationId, data) { const { sourceSwimlaneId, targetBoardId, copyOptions } = data; const operation = boardOperations.get().get(operationId); - + // Simulate copy progress const steps = ['copying_swimlane', 'copying_lists', 'copying_cards', 'finalizing']; for (let i = 0; i < steps.length; i++) { operation.progress = Math.round(((i + 1) / steps.length) * 100); this.updateBoardOperation(operationId, operation); - + // Simulate work await new Promise(resolve => setTimeout(resolve, 500)); } @@ -2319,13 +1115,13 @@ class CronMigrationManager { async moveSwimlane(operationId, data) { const { sourceSwimlaneId, targetBoardId, moveOptions } = data; const operation = boardOperations.get().get(operationId); - + // Simulate move progress const steps = ['preparing_move', 'moving_swimlane', 'updating_references', 'finalizing']; for (let i = 0; i < steps.length; i++) { operation.progress = Math.round(((i + 1) / steps.length) * 100); this.updateBoardOperation(operationId, operation); - + // Simulate work await new Promise(resolve => setTimeout(resolve, 400)); } @@ -2337,13 +1133,13 @@ class CronMigrationManager { async copyList(operationId, data) { const { sourceListId, targetBoardId, copyOptions } = data; const operation = boardOperations.get().get(operationId); - + // Simulate copy progress const steps = ['copying_list', 'copying_cards', 'copying_attachments', 'finalizing']; for (let i = 0; i < steps.length; i++) { operation.progress = Math.round(((i + 1) / steps.length) * 100); this.updateBoardOperation(operationId, operation); - + // Simulate work await new Promise(resolve => setTimeout(resolve, 300)); } @@ -2355,13 +1151,13 @@ class CronMigrationManager { async moveList(operationId, data) { const { sourceListId, targetBoardId, moveOptions } = data; const operation = boardOperations.get().get(operationId); - + // Simulate move progress const steps = ['preparing_move', 'moving_list', 'updating_references', 'finalizing']; for (let i = 0; i < steps.length; i++) { operation.progress = Math.round(((i + 1) / steps.length) * 100); this.updateBoardOperation(operationId, operation); - + // Simulate work await new Promise(resolve => setTimeout(resolve, 200)); } @@ -2373,13 +1169,13 @@ class CronMigrationManager { async copyCard(operationId, data) { const { sourceCardId, targetListId, copyOptions } = data; const operation = boardOperations.get().get(operationId); - + // Simulate copy progress const steps = ['copying_card', 'copying_attachments', 'copying_checklists', 'finalizing']; for (let i = 0; i < steps.length; i++) { operation.progress = Math.round(((i + 1) / steps.length) * 100); this.updateBoardOperation(operationId, operation); - + // Simulate work await new Promise(resolve => setTimeout(resolve, 150)); } @@ -2391,13 +1187,13 @@ class CronMigrationManager { async moveCard(operationId, data) { const { sourceCardId, targetListId, moveOptions } = data; const operation = boardOperations.get().get(operationId); - + // Simulate move progress const steps = ['preparing_move', 'moving_card', 'updating_references', 'finalizing']; for (let i = 0; i < steps.length; i++) { operation.progress = Math.round(((i + 1) / steps.length) * 100); this.updateBoardOperation(operationId, operation); - + // Simulate work await new Promise(resolve => setTimeout(resolve, 100)); } @@ -2409,13 +1205,13 @@ class CronMigrationManager { async copyChecklist(operationId, data) { const { sourceChecklistId, targetCardId, copyOptions } = data; const operation = boardOperations.get().get(operationId); - + // Simulate copy progress const steps = ['copying_checklist', 'copying_items', 'finalizing']; for (let i = 0; i < steps.length; i++) { operation.progress = Math.round(((i + 1) / steps.length) * 100); this.updateBoardOperation(operationId, operation); - + // Simulate work await new Promise(resolve => setTimeout(resolve, 100)); } @@ -2427,13 +1223,13 @@ class CronMigrationManager { async moveChecklist(operationId, data) { const { sourceChecklistId, targetCardId, moveOptions } = data; const operation = boardOperations.get().get(operationId); - + // Simulate move progress const steps = ['preparing_move', 'moving_checklist', 'finalizing']; for (let i = 0; i < steps.length; i++) { operation.progress = Math.round(((i + 1) / steps.length) * 100); this.updateBoardOperation(operationId, operation); - + // Simulate work await new Promise(resolve => setTimeout(resolve, 50)); } @@ -2445,13 +1241,13 @@ class CronMigrationManager { getBoardOperations(boardId) { const operations = boardOperations.get(); const boardOps = []; - + for (const [operationId, operation] of operations) { if (operation.boardId === boardId) { boardOps.push(operation); } } - + return boardOps.sort((a, b) => b.startTime - a.startTime); } @@ -2461,24 +1257,24 @@ class CronMigrationManager { getAllBoardOperations(page = 1, limit = 20, searchTerm = '') { const operations = boardOperations.get(); const allOps = Array.from(operations.values()); - + // Filter by search term if provided let filteredOps = allOps; if (searchTerm) { - filteredOps = allOps.filter(op => + filteredOps = allOps.filter(op => op.boardId.toLowerCase().includes(searchTerm.toLowerCase()) || op.type.toLowerCase().includes(searchTerm.toLowerCase()) ); } - + // Sort by start time (newest first) filteredOps.sort((a, b) => b.startTime - a.startTime); - + // Paginate const startIndex = (page - 1) * limit; const endIndex = startIndex + limit; const paginatedOps = filteredOps.slice(startIndex, endIndex); - + return { operations: paginatedOps, total: filteredOps.length, @@ -2500,16 +1296,16 @@ class CronMigrationManager { error: 0, byType: {} }; - + for (const [operationId, operation] of operations) { stats[operation.status]++; - + if (!stats.byType[operation.type]) { stats.byType[operation.type] = 0; } stats.byType[operation.type]++; } - + return stats; } @@ -2519,8 +1315,8 @@ class CronMigrationManager { clearAllCronJobs() { try { // Stop all existing cron jobs - if (SyncedCron?._entries) { - Object.values(SyncedCron._entries).forEach(job => { + if (SyncedCron && SyncedCron.jobs) { + SyncedCron.jobs.forEach(job => { try { SyncedCron.remove(job.name); } catch (error) { @@ -2548,219 +1344,6 @@ class CronMigrationManager { } } - /** - * Pause all migrations - */ - pauseAllMigrations() { - this.isRunning = false; - cronIsMigrating.set(false); - cronMigrationStatus.set('Migrations paused'); - - // Update CronJobStatus for immediate pub/sub notification - CronJobStatus.upsert( - { jobId: 'migration' }, - { - $set: { - jobId: 'migration', - status: 'pausing', - statusMessage: 'Pausing migrations...', - updatedAt: new Date() - } - } - ); - - // Update all pending jobs in queue to paused - const pendingJobs = cronJobStorage.getIncompleteJobs(); - pendingJobs.forEach(job => { - if (job.status === 'pending' || job.status === 'running') { - cronJobStorage.updateQueueStatus(job.jobId, 'paused'); - cronJobStorage.saveJobStatus(job.jobId, { status: 'paused' }); - } - }); - - // Update to final paused state - CronJobStatus.upsert( - { jobId: 'migration' }, - { - $set: { - jobId: 'migration', - status: 'paused', - statusMessage: 'Migrations paused', - updatedAt: new Date() - } - } - ); - - return { success: true, message: 'All migrations paused' }; - } - - /** - * Stop all migrations - */ - stopAllMigrations() { - // Update CronJobStatus for immediate pub/sub notification - CronJobStatus.upsert( - { jobId: 'migration' }, - { - $set: { - jobId: 'migration', - status: 'stopping', - statusMessage: 'Stopping migrations...', - updatedAt: new Date() - } - } - ); - - // Clear monitor interval first to prevent status override - if (this.monitorInterval) { - Meteor.clearInterval(this.monitorInterval); - this.monitorInterval = null; - } - - // Stop all running and pending jobs - const incompleteJobs = cronJobStorage.getIncompleteJobs(); - incompleteJobs.forEach(job => { - cronJobStorage.updateQueueStatus(job.jobId, 'stopped', { stoppedAt: new Date() }); - cronJobStorage.saveJobStatus(job.jobId, { - status: 'stopped', - stoppedAt: new Date() - }); - }); - - // Reset migration state immediately - this.isRunning = false; - cronIsMigrating.set(false); - cronMigrationProgress.set(0); - cronMigrationCurrentStep.set(''); - cronMigrationCurrentStepNum.set(0); - cronMigrationTotalSteps.set(0); - cronMigrationStatus.set('All migrations stopped'); - - // Update to final stopped state - CronJobStatus.upsert( - { jobId: 'migration' }, - { - $set: { - jobId: 'migration', - status: 'stopped', - statusMessage: 'All migrations stopped', - progress: 0, - updatedAt: new Date() - } - } - ); - - // Clear status message after delay - Meteor.setTimeout(() => { - cronMigrationStatus.set(''); - CronJobStatus.upsert( - { jobId: 'migration' }, - { - $set: { - statusMessage: '', - updatedAt: new Date() - } - } - ); - }, 3000); - - return { success: true, message: 'All migrations stopped' }; - } - - /** - * Resume all paused migrations - */ - resumeAllMigrations() { - // Find all paused jobs and resume them - const pausedJobs = CronJobStatus.find({ status: 'paused' }).fetch(); - - if (pausedJobs.length === 0) { - return { success: false, message: 'No paused migrations to resume' }; - } - - pausedJobs.forEach(job => { - cronJobStorage.updateQueueStatus(job.jobId, 'pending'); - cronJobStorage.saveJobStatus(job.jobId, { status: 'pending' }); - }); - - this.isRunning = true; - cronIsMigrating.set(true); - cronMigrationStatus.set('Resuming migrations...'); - - // Restart monitoring - this.monitorMigrationProgress(); - - return { success: true, message: `Resumed ${pausedJobs.length} migrations` }; - } - - /** - * Retry failed migrations - */ - retryFailedMigrations() { - const failedJobs = CronJobStatus.find({ status: 'failed' }).fetch(); - - if (failedJobs.length === 0) { - return { success: false, message: 'No failed migrations to retry' }; - } - - // Clear errors for failed jobs - failedJobs.forEach(job => { - cronJobStorage.clearJobErrors(job.jobId); - cronJobStorage.updateQueueStatus(job.jobId, 'pending'); - cronJobStorage.saveJobStatus(job.jobId, { - status: 'pending', - progress: 0, - error: null - }); - }); - - if (!this.isRunning) { - this.isRunning = true; - cronIsMigrating.set(true); - cronMigrationStatus.set('Retrying failed migrations...'); - this.monitorMigrationProgress(); - } - - return { success: true, message: `Retrying ${failedJobs.length} failed migrations` }; - } - - /** - * Get all migration errors - */ - getAllMigrationErrors(limit = 50) { - return cronJobStorage.getAllRecentErrors(limit); - } - - /** - * Get errors for a specific job - */ - getJobErrors(jobId, options = {}) { - return cronJobStorage.getJobErrors(jobId, options); - } - - /** - * Get migration stats including errors - */ - getMigrationStats() { - const queueStats = cronJobStorage.getQueueStats(); - const allErrors = cronJobStorage.getAllRecentErrors(100); - const errorsByJob = {}; - - allErrors.forEach(error => { - if (!errorsByJob[error.jobId]) { - errorsByJob[error.jobId] = []; - } - errorsByJob[error.jobId].push(error); - }); - - return { - ...queueStats, - totalErrors: allErrors.length, - errorsByJob, - recentErrors: allErrors.slice(0, 10) - }; - } - } // Export singleton instance @@ -2773,602 +1356,196 @@ Meteor.startup(() => { // Meteor methods for client-server communication Meteor.methods({ - async 'cron.startAllMigrations'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.startAllMigrations'() { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + return cronMigrationManager.startAllMigrations(); }, - async 'cron.startSpecificMigration'(migrationIndex) { - check(migrationIndex, Number); - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.startJob'(cronName) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - return cronMigrationManager.startSpecificMigration(migrationIndex); - }, - - async 'cron.startJob'(cronName) { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + return cronMigrationManager.startCronJob(cronName); }, - async 'cron.stopJob'(cronName) { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.stopJob'(cronName) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + return cronMigrationManager.stopCronJob(cronName); }, - async 'cron.pauseJob'(cronName) { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.pauseJob'(cronName) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + return cronMigrationManager.pauseCronJob(cronName); }, - async 'cron.resumeJob'(cronName) { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.resumeJob'(cronName) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + return cronMigrationManager.resumeCronJob(cronName); }, - async 'cron.removeJob'(cronName) { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.removeJob'(cronName) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + return cronMigrationManager.removeCronJob(cronName); }, - async 'cron.addJob'(jobData) { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.addJob'(jobData) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + return cronMigrationManager.addCronJob(jobData); }, - async 'cron.getJobs'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + 'cron.getJobs'() { return cronMigrationManager.getAllCronJobs(); }, - async 'cron.getMigrationProgress'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - const runningJob = CronJobStatus.findOne( - { status: 'running', jobType: 'migration' }, - { sort: { updatedAt: -1 } } - ); - - let currentAction = ''; - let jobProgress = 0; - let jobStepNum = 0; - let jobTotalSteps = 0; - let etaSeconds = null; - let elapsedSeconds = null; - - let migrationNumber = null; - let migrationName = ''; - - if (runningJob) { - jobProgress = runningJob.progress || 0; - - const steps = cronJobStorage.getJobSteps(runningJob.jobId); - jobTotalSteps = steps.length; - const runningStep = steps.find(step => step.status === 'running') || steps[steps.length - 1]; - - if (runningStep) { - currentAction = runningStep.currentAction || runningStep.stepName || ''; - jobStepNum = (runningStep.stepIndex || 0) + 1; - } - - const startedAt = runningJob.startedAt || runningJob.createdAt || runningJob.updatedAt; - if (startedAt) { - elapsedSeconds = Math.max(0, Math.round((Date.now() - startedAt.getTime()) / 1000)); - if (jobProgress > 0) { - etaSeconds = Math.max(0, Math.round((elapsedSeconds * (100 - jobProgress)) / jobProgress)); - } - } - - if (runningJob.stepId) { - const steps = cronMigrationManager.getMigrationSteps(); - const index = steps.findIndex(step => step.id === runningJob.stepId); - if (index >= 0) { - migrationNumber = index + 1; - migrationName = steps[index].name; - } - } - } - - const migrationStepsLoaded = cronMigrationSteps.get().length; - const migrationStepsTotal = cronMigrationManager.getMigrationSteps().length; - + 'cron.getMigrationProgress'() { return { progress: cronMigrationProgress.get(), status: cronMigrationStatus.get(), currentStep: cronMigrationCurrentStep.get(), steps: cronMigrationSteps.get(), - isMigrating: cronIsMigrating.get(), - currentStepNum: cronMigrationCurrentStepNum.get(), - totalSteps: cronMigrationTotalSteps.get(), - migrationStepsLoaded, - migrationStepsTotal, - currentAction, - jobProgress, - jobStepNum, - jobTotalSteps, - etaSeconds, - elapsedSeconds, - migrationNumber, - migrationName + isMigrating: cronIsMigrating.get() }; }, - async 'cron.pauseAllMigrations'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.startBoardOperation'(boardId, operationType, operationData) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - return cronMigrationManager.pauseAllMigrations(); - }, - - async 'cron.stopAllMigrations'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - return cronMigrationManager.stopAllMigrations(); - }, - - async 'cron.stopAllMigrations'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - return cronMigrationManager.stopAllMigrations(); - }, - - async 'cron.resumeAllMigrations'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - return cronMigrationManager.resumeAllMigrations(); - }, - - async 'cron.retryFailedMigrations'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - return cronMigrationManager.retryFailedMigrations(); - }, - - async 'cron.getAllMigrationErrors'(limit = 50) { - check(limit, Match.Optional(Number)); - - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - return cronMigrationManager.getAllMigrationErrors(limit); - }, - - async 'cron.getJobErrors'(jobId, options = {}) { - check(jobId, String); - check(options, Match.Optional(Object)); - - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - return cronMigrationManager.getJobErrors(jobId, options); - }, - - async 'cron.getMigrationStats'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - return cronMigrationManager.getMigrationStats(); - }, - - async 'cron.startBoardOperation'(boardId, operationType, operationData) { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - - // Check if user is global admin OR board admin - const user = await ReactiveCache.getUser(userId); - const board = await ReactiveCache.getBoard(boardId); - - if (!user) { - throw new Meteor.Error('not-authorized', 'User not found'); - } - - if (!board) { - throw new Meteor.Error('not-found', 'Board not found'); - } - - // Check global admin or board admin - const isGlobalAdmin = user.isAdmin; - const isBoardAdmin = board.members && board.members.some(member => - member.userId === userId && member.isAdmin - ); - - if (!isGlobalAdmin && !isBoardAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required for this board'); - } - + return cronMigrationManager.startBoardOperation(boardId, operationType, operationData); }, - async 'cron.getBoardOperations'(boardId) { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.getBoardOperations'(boardId) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - - // Check if user is global admin OR board admin - const user = await ReactiveCache.getUser(userId); - const board = await ReactiveCache.getBoard(boardId); - - if (!user) { - throw new Meteor.Error('not-authorized', 'User not found'); - } - - if (!board) { - throw new Meteor.Error('not-found', 'Board not found'); - } - - // Check global admin or board admin - const isGlobalAdmin = user.isAdmin; - const isBoardAdmin = board.members && board.members.some(member => - member.userId === userId && member.isAdmin - ); - - if (!isGlobalAdmin && !isBoardAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required for this board'); - } - + return cronMigrationManager.getBoardOperations(boardId); }, - async 'cron.getAllBoardOperations'(page, limit, searchTerm) { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.getAllBoardOperations'(page, limit, searchTerm) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + return cronMigrationManager.getAllBoardOperations(page, limit, searchTerm); }, - async 'cron.getBoardOperationStats'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.getBoardOperationStats'() { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + return cronMigrationManager.getBoardOperationStats(); }, - async 'cron.getJobDetails'(jobId) { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.getJobDetails'(jobId) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + return cronJobStorage.getJobDetails(jobId); }, - async 'cron.getQueueStats'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.getQueueStats'() { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + return cronJobStorage.getQueueStats(); }, - async 'cron.getSystemResources'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.getSystemResources'() { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + return cronJobStorage.getSystemResources(); }, - async 'cron.clearAllJobs'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.clearAllJobs'() { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + return cronMigrationManager.clearAllCronJobs(); }, - async 'cron.pauseJob'(jobId) { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.pauseJob'(jobId) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + cronJobStorage.updateQueueStatus(jobId, 'paused'); cronJobStorage.saveJobStatus(jobId, { status: 'paused' }); return { success: true }; }, - async 'cron.resumeJob'(jobId) { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.resumeJob'(jobId) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + cronJobStorage.updateQueueStatus(jobId, 'pending'); cronJobStorage.saveJobStatus(jobId, { status: 'pending' }); return { success: true }; }, - async 'cron.stopJob'(jobId) { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.stopJob'(jobId) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + cronJobStorage.updateQueueStatus(jobId, 'stopped'); - cronJobStorage.saveJobStatus(jobId, { + cronJobStorage.saveJobStatus(jobId, { status: 'stopped', stoppedAt: new Date() }); return { success: true }; }, - async 'cron.cleanupOldJobs'(daysOld) { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.cleanupOldJobs'(daysOld) { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + return cronJobStorage.cleanupOldJobs(daysOld); }, - async 'cron.pauseAllMigrations'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.getBoardMigrationStats'() { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - // Pause all running jobs in the queue - const runningJobs = cronJobStorage.getIncompleteJobs().filter(job => job.status === 'running'); - runningJobs.forEach(job => { - cronJobStorage.updateQueueStatus(job.jobId, 'paused'); - cronJobStorage.saveJobStatus(job.jobId, { status: 'paused' }); - }); - - cronMigrationStatus.set('All migrations paused'); - return { success: true, message: 'All migrations paused' }; - }, - - async 'cron.stopAllMigrations'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - - // Clear monitor interval first to prevent status override - if (cronMigrationManager.monitorInterval) { - Meteor.clearInterval(cronMigrationManager.monitorInterval); - cronMigrationManager.monitorInterval = null; - } - - // Stop all running and pending jobs - const incompleteJobs = cronJobStorage.getIncompleteJobs(); - incompleteJobs.forEach(job => { - cronJobStorage.updateQueueStatus(job.jobId, 'stopped', { stoppedAt: new Date() }); - cronJobStorage.saveJobStatus(job.jobId, { - status: 'stopped', - stoppedAt: new Date() - }); - }); - - // Reset migration state immediately - cronMigrationManager.isRunning = false; - cronIsMigrating.set(false); - cronMigrationProgress.set(0); - cronMigrationCurrentStep.set(''); - cronMigrationCurrentStepNum.set(0); - cronMigrationTotalSteps.set(0); - cronMigrationStatus.set('All migrations stopped'); - - // Clear status message after delay - setTimeout(() => { - cronMigrationStatus.set(''); - }, 3000); - - return { success: true, message: 'All migrations stopped' }; - }, - - async 'cron.getBoardMigrationStats'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); - } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + // Import the board migration detector const { boardMigrationDetector } = require('./boardMigrationDetector'); return boardMigrationDetector.getMigrationStats(); }, - async 'cron.forceBoardMigrationScan'() { - const userId = this.userId; - if (!userId) { - throw new Meteor.Error('not-authorized', 'Must be logged in'); + 'cron.forceBoardMigrationScan'() { + if (!this.userId) { + throw new Meteor.Error('not-authorized'); } - const user = await ReactiveCache.getUser(userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin access required'); - } - + // Import the board migration detector const { boardMigrationDetector } = require('./boardMigrationDetector'); return boardMigrationDetector.forceScan(); diff --git a/server/lib/customHeadRender.js b/server/lib/customHeadRender.js deleted file mode 100644 index e6d84cacb..000000000 --- a/server/lib/customHeadRender.js +++ /dev/null @@ -1,54 +0,0 @@ -import { WebApp } from 'meteor/webapp'; -import { WebAppInternals } from 'meteor/webapp'; -import Settings from '/models/settings'; - -Meteor.startup(() => { - // Use Meteor's official API to modify the HTML boilerplate - WebAppInternals.registerBoilerplateDataCallback('wekan-custom-head', (request, data) => { - try { - const setting = Settings.findOne(); - - // Initialize head array if it doesn't exist - if (!data.head) { - data.head = ''; - } - - // Always set title tag based on productName - const productName = (setting && setting.productName) ? setting.productName : 'Wekan'; - data.head += `\n <title>${productName}\n`; - - // Only add custom head tags if enabled - if (!setting || !setting.customHeadEnabled) { - return data; - } - - let injection = ''; - // Add custom link tags (except manifest if custom manifest is enabled) - if (setting.customHeadLinkTags && setting.customHeadLinkTags.trim()) { - let linkTags = setting.customHeadLinkTags; - if (setting.customManifestEnabled) { - // Remove any manifest links from custom link tags to avoid duplicates - linkTags = linkTags.replace(/]*rel=["\']?manifest["\']?[^>]*>/gi, ''); - } - if (linkTags.trim()) { - injection += linkTags + '\n'; - } - } - - // Add manifest link if custom manifest is enabled - if (setting.customManifestEnabled) { - injection += ' \n'; - } - - if (injection.trim()) { - // Append custom head content to the existing head - data.head += injection; - } - - return data; - } catch (e) { - console.error('[Custom Head] Error in boilerplate callback:', e.message, e.stack); - return data; - } - }); -}); diff --git a/server/lib/emailLocalization.js b/server/lib/emailLocalization.js index 19f0e8992..4c8bd0b17 100644 --- a/server/lib/emailLocalization.js +++ b/server/lib/emailLocalization.js @@ -17,13 +17,13 @@ EmailLocalization = { * @param {String} options.language - Language code to use (if not provided, will try to detect) * @param {String} options.userId - User ID to determine language (if not provided with language) */ - async sendEmail(options) { + sendEmail(options) { // Determine the language to use let lang = options.language; // If no language is specified but we have a userId, try to get the user's language if (!lang && options.userId) { - const user = await ReactiveCache.getUser(options.userId); + const user = ReactiveCache.getUser(options.userId); if (user) { lang = user.getLanguage(); } diff --git a/server/lib/tests/attachmentApi.tests.js b/server/lib/tests/attachmentApi.tests.js index 2c3b80a48..1b89c236a 100644 --- a/server/lib/tests/attachmentApi.tests.js +++ b/server/lib/tests/attachmentApi.tests.js @@ -161,7 +161,7 @@ describe('attachmentApi authentication', function() { describe('request handler DoS prevention', function() { it('enforces timeout on hanging requests', function(done) { this.timeout(5000); - + const req = createMockReq({ 'x-user-id': 'user1', 'x-auth-token': 'token1' }); const res = createMockRes(); diff --git a/server/lib/utils.js b/server/lib/utils.js index d4f5667bb..b194bb246 100644 --- a/server/lib/utils.js +++ b/server/lib/utils.js @@ -13,26 +13,15 @@ allowIsAnyBoardMember = function(userId, boards) { }; allowIsBoardMemberCommentOnly = function(userId, board) { - return board && board.hasMember(userId) && !board.hasReadOnly(userId) && !board.hasReadAssignedOnly(userId) && !board.hasNoComments(userId); + return board && board.hasMember(userId) && !board.hasCommentOnly(userId); }; allowIsBoardMemberNoComments = function(userId, board) { return board && board.hasMember(userId) && !board.hasNoComments(userId); }; -// Check if user has write access to board (can create/edit cards and lists) -allowIsBoardMemberWithWriteAccess = function(userId, board) { - return board && board.members && board.members.some(e => e.userId === userId && e.isActive && !e.isNoComments && !e.isCommentOnly && !e.isWorker && !e.isReadOnly && !e.isReadAssignedOnly); -}; - -// Check if user has write access via a card's board -allowIsBoardMemberWithWriteAccessByCard = function(userId, card) { - const board = card && Boards.findOne(card.boardId); - return allowIsBoardMemberWithWriteAccess(userId, board); -}; - allowIsBoardMemberByCard = function(userId, card) { - const board = card && Boards.findOne(card.boardId); + const board = card.board(); return board && board.hasMember(userId); }; diff --git a/server/methods/fixDuplicateLists.js b/server/methods/fixDuplicateLists.js index 12aebc069..b673021ec 100644 --- a/server/methods/fixDuplicateLists.js +++ b/server/methods/fixDuplicateLists.js @@ -4,26 +4,21 @@ import Boards from '/models/boards'; import Lists from '/models/lists'; import Swimlanes from '/models/swimlanes'; import Cards from '/models/cards'; -import { ReactiveCache } from '/imports/reactiveCache'; /** * Fix duplicate lists and swimlanes created by WeKan 8.10 * This method identifies and removes duplicate lists while preserving cards */ Meteor.methods({ - async 'fixDuplicateLists.fixAllBoards'() { + 'fixDuplicateLists.fixAllBoards'() { if (!this.userId) { throw new Meteor.Error('not-authorized'); } - if (!(await ReactiveCache.getUser(this.userId)).isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin required'); - } - if (process.env.DEBUG === 'true') { console.log('Starting duplicate lists fix for all boards...'); } - + const allBoards = Boards.find({}).fetch(); let totalFixed = 0; let totalBoardsProcessed = 0; @@ -33,7 +28,7 @@ Meteor.methods({ const result = fixDuplicateListsForBoard(board._id); totalFixed += result.fixed; totalBoardsProcessed++; - + if (result.fixed > 0 && process.env.DEBUG === 'true') { console.log(`Fixed ${result.fixed} duplicate lists in board "${board.title}" (${board._id})`); } @@ -45,7 +40,7 @@ Meteor.methods({ if (process.env.DEBUG === 'true') { console.log(`Duplicate lists fix completed. Processed ${totalBoardsProcessed} boards, fixed ${totalFixed} duplicate lists.`); } - + return { message: `Fixed ${totalFixed} duplicate lists across ${totalBoardsProcessed} boards`, totalFixed, @@ -53,18 +48,13 @@ Meteor.methods({ }; }, - async 'fixDuplicateLists.fixBoard'(boardId) { + 'fixDuplicateLists.fixBoard'(boardId) { check(boardId, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - const board = await ReactiveCache.getBoard(boardId); - if (!board || !board.hasAdmin(this.userId)) { - throw new Meteor.Error('not-authorized'); - } - return fixDuplicateListsForBoard(boardId); } }); @@ -74,13 +64,13 @@ function fixDuplicateListsForBoard(boardId) { if (process.env.DEBUG === 'true') { console.log(`Fixing duplicate lists for board ${boardId}...`); } - + // First, fix duplicate swimlanes const swimlaneResult = fixDuplicateSwimlanes(boardId); - + // Then, fix duplicate lists const listResult = fixDuplicateLists(boardId); - + return { boardId, fixedSwimlanes: swimlaneResult.fixed, @@ -193,7 +183,7 @@ function fixDuplicateLists(boardId) { { $set: { listId: keepList._id } }, { multi: true } ); - + // Remove duplicate list Lists.remove(list._id); fixed++; @@ -208,22 +198,18 @@ function fixDuplicateLists(boardId) { } Meteor.methods({ - async 'fixDuplicateLists.getReport'() { + 'fixDuplicateLists.getReport'() { if (!this.userId) { throw new Meteor.Error('not-authorized'); } - if (!(await ReactiveCache.getUser(this.userId)).isAdmin) { - throw new Meteor.Error('not-authorized', 'Admin required'); - } - const allBoards = Boards.find({}).fetch(); const report = []; for (const board of allBoards) { const swimlanes = Swimlanes.find({ boardId: board._id }).fetch(); const lists = Lists.find({ boardId: board._id }).fetch(); - + // Check for duplicate swimlanes const swimlaneGroups = {}; swimlanes.forEach(swimlane => { diff --git a/server/methods/lockedUsers.js b/server/methods/lockedUsers.js index 7458b832e..e4eaf8bbc 100644 --- a/server/methods/lockedUsers.js +++ b/server/methods/lockedUsers.js @@ -2,13 +2,13 @@ import { ReactiveCache } from '/imports/reactiveCache'; // Method to find locked users and release them if needed Meteor.methods({ - async getLockedUsers() { + getLockedUsers() { // Check if user has admin rights const userId = Meteor.userId(); if (!userId) { throw new Meteor.Error('error-invalid-user', 'Invalid user'); } - const user = await ReactiveCache.getUser(userId); + const user = ReactiveCache.getUser(userId); if (!user || !user.isAdmin) { throw new Meteor.Error('error-not-allowed', 'Not allowed'); } @@ -17,7 +17,7 @@ Meteor.methods({ const currentTime = Number(new Date()); // Find users that are locked (known users) - const lockedUsers = await Meteor.users.find( + const lockedUsers = Meteor.users.find( { 'services.accounts-lockout.unlockTime': { $gt: currentTime, @@ -32,7 +32,7 @@ Meteor.methods({ 'services.accounts-lockout.failedAttempts': 1 } } - ).fetchAsync(); + ).fetch(); // Format the results for the UI return lockedUsers.map(user => { @@ -50,25 +50,25 @@ Meteor.methods({ }); }, - async unlockUser(userId) { + unlockUser(userId) { // Check if user has admin rights const adminId = Meteor.userId(); if (!adminId) { throw new Meteor.Error('error-invalid-user', 'Invalid user'); } - const admin = await ReactiveCache.getUser(adminId); + const admin = ReactiveCache.getUser(adminId); if (!admin || !admin.isAdmin) { throw new Meteor.Error('error-not-allowed', 'Not allowed'); } // Make sure the user to unlock exists - const userToUnlock = await Meteor.users.findOneAsync(userId); + const userToUnlock = Meteor.users.findOne(userId); if (!userToUnlock) { throw new Meteor.Error('error-user-not-found', 'User not found'); } // Unlock the user - await Meteor.users.updateAsync( + Meteor.users.update( { _id: userId }, { $unset: { @@ -80,19 +80,19 @@ Meteor.methods({ return true; }, - async unlockAllUsers() { + unlockAllUsers() { // Check if user has admin rights const adminId = Meteor.userId(); if (!adminId) { throw new Meteor.Error('error-invalid-user', 'Invalid user'); } - const admin = await ReactiveCache.getUser(adminId); + const admin = ReactiveCache.getUser(adminId); if (!admin || !admin.isAdmin) { throw new Meteor.Error('error-not-allowed', 'Not allowed'); } // Unlock all users - await Meteor.users.updateAsync( + Meteor.users.update( { 'services.accounts-lockout.unlockTime': { $exists: true } }, { $unset: { diff --git a/server/methods/lockoutSettings.js b/server/methods/lockoutSettings.js index 70b8efead..047999bdc 100644 --- a/server/methods/lockoutSettings.js +++ b/server/methods/lockoutSettings.js @@ -3,13 +3,13 @@ import { ReactiveCache } from '/imports/reactiveCache'; import LockoutSettings from '/models/lockoutSettings'; Meteor.methods({ - async reloadAccountsLockout() { + reloadAccountsLockout() { // Check if user has admin rights const userId = Meteor.userId(); if (!userId) { throw new Meteor.Error('error-invalid-user', 'Invalid user'); } - const user = await ReactiveCache.getUser(userId); + const user = ReactiveCache.getUser(userId); if (!user || !user.isAdmin) { throw new Meteor.Error('error-not-allowed', 'Not allowed'); } @@ -17,15 +17,15 @@ Meteor.methods({ try { // Get configurations from database const knownUsersConfig = { - failuresBeforeLockout: (await LockoutSettings.findOneAsync('known-failuresBeforeLockout'))?.value || 3, - lockoutPeriod: (await LockoutSettings.findOneAsync('known-lockoutPeriod'))?.value || 60, - failureWindow: (await LockoutSettings.findOneAsync('known-failureWindow'))?.value || 15 + failuresBeforeLockout: LockoutSettings.findOne('known-failuresBeforeLockout')?.value || 3, + lockoutPeriod: LockoutSettings.findOne('known-lockoutPeriod')?.value || 60, + failureWindow: LockoutSettings.findOne('known-failureWindow')?.value || 15 }; const unknownUsersConfig = { - failuresBeforeLockout: (await LockoutSettings.findOneAsync('unknown-failuresBeforeLockout'))?.value || 3, - lockoutPeriod: (await LockoutSettings.findOneAsync('unknown-lockoutPeriod'))?.value || 60, - failureWindow: (await LockoutSettings.findOneAsync('unknown-failureWindow'))?.value || 15 + failuresBeforeLockout: LockoutSettings.findOne('unknown-failuresBeforeLockout')?.value || 3, + lockoutPeriod: LockoutSettings.findOne('unknown-lockoutPeriod')?.value || 60, + failureWindow: LockoutSettings.findOne('unknown-failureWindow')?.value || 15 }; // Initialize the AccountsLockout with configuration diff --git a/server/methods/positionHistory.js b/server/methods/positionHistory.js index 95a585a88..c16b874a1 100644 --- a/server/methods/positionHistory.js +++ b/server/methods/positionHistory.js @@ -4,7 +4,6 @@ import PositionHistory from '/models/positionHistory'; import Swimlanes from '/models/swimlanes'; import Lists from '/models/lists'; import Cards from '/models/cards'; -import { ReactiveCache } from '/imports/reactiveCache'; /** * Server-side methods for position history tracking @@ -13,326 +12,200 @@ Meteor.methods({ /** * Track original position for a swimlane */ - async 'positionHistory.trackSwimlane'(swimlaneId) { + 'positionHistory.trackSwimlane'(swimlaneId) { check(swimlaneId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const swimlane = await Swimlanes.findOneAsync(swimlaneId); + + const swimlane = Swimlanes.findOne(swimlaneId); if (!swimlane) { throw new Meteor.Error('swimlane-not-found', 'Swimlane not found'); } - - const board = await ReactiveCache.getBoard(swimlane.boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - + return swimlane.trackOriginalPosition(); }, /** * Track original position for a list */ - async 'positionHistory.trackList'(listId) { + 'positionHistory.trackList'(listId) { check(listId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const list = await Lists.findOneAsync(listId); + + const list = Lists.findOne(listId); if (!list) { throw new Meteor.Error('list-not-found', 'List not found'); } - - const board = await ReactiveCache.getBoard(list.boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - + return list.trackOriginalPosition(); }, /** * Track original position for a card */ - async 'positionHistory.trackCard'(cardId) { + 'positionHistory.trackCard'(cardId) { check(cardId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const card = await Cards.findOneAsync(cardId); + + const card = Cards.findOne(cardId); if (!card) { throw new Meteor.Error('card-not-found', 'Card not found'); } - - const board = await ReactiveCache.getBoard(card.boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - + return card.trackOriginalPosition(); }, /** * Get original position for a swimlane */ - async 'positionHistory.getSwimlaneOriginalPosition'(swimlaneId) { + 'positionHistory.getSwimlaneOriginalPosition'(swimlaneId) { check(swimlaneId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const swimlane = await Swimlanes.findOneAsync(swimlaneId); + + const swimlane = Swimlanes.findOne(swimlaneId); if (!swimlane) { throw new Meteor.Error('swimlane-not-found', 'Swimlane not found'); } - - const board = await ReactiveCache.getBoard(swimlane.boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - + return swimlane.getOriginalPosition(); }, /** * Get original position for a list */ - async 'positionHistory.getListOriginalPosition'(listId) { + 'positionHistory.getListOriginalPosition'(listId) { check(listId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const list = await Lists.findOneAsync(listId); + + const list = Lists.findOne(listId); if (!list) { throw new Meteor.Error('list-not-found', 'List not found'); } - - const board = await ReactiveCache.getBoard(list.boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - + return list.getOriginalPosition(); }, /** * Get original position for a card */ - async 'positionHistory.getCardOriginalPosition'(cardId) { + 'positionHistory.getCardOriginalPosition'(cardId) { check(cardId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const card = await Cards.findOneAsync(cardId); + + const card = Cards.findOne(cardId); if (!card) { throw new Meteor.Error('card-not-found', 'Card not found'); } - - const board = await ReactiveCache.getBoard(card.boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - + return card.getOriginalPosition(); }, /** * Check if a swimlane has moved from its original position */ - async 'positionHistory.hasSwimlaneMoved'(swimlaneId) { + 'positionHistory.hasSwimlaneMoved'(swimlaneId) { check(swimlaneId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const swimlane = await Swimlanes.findOneAsync(swimlaneId); + + const swimlane = Swimlanes.findOne(swimlaneId); if (!swimlane) { throw new Meteor.Error('swimlane-not-found', 'Swimlane not found'); } - - const board = await ReactiveCache.getBoard(swimlane.boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - + return swimlane.hasMovedFromOriginalPosition(); }, /** * Check if a list has moved from its original position */ - async 'positionHistory.hasListMoved'(listId) { + 'positionHistory.hasListMoved'(listId) { check(listId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const list = await Lists.findOneAsync(listId); + + const list = Lists.findOne(listId); if (!list) { throw new Meteor.Error('list-not-found', 'List not found'); } - - const board = await ReactiveCache.getBoard(list.boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - + return list.hasMovedFromOriginalPosition(); }, /** * Check if a card has moved from its original position */ - async 'positionHistory.hasCardMoved'(cardId) { + 'positionHistory.hasCardMoved'(cardId) { check(cardId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const card = await Cards.findOneAsync(cardId); + + const card = Cards.findOne(cardId); if (!card) { throw new Meteor.Error('card-not-found', 'Card not found'); } - - const board = await ReactiveCache.getBoard(card.boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - + return card.hasMovedFromOriginalPosition(); }, /** * Get original position description for a swimlane */ - async 'positionHistory.getSwimlaneDescription'(swimlaneId) { + 'positionHistory.getSwimlaneDescription'(swimlaneId) { check(swimlaneId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const swimlane = await Swimlanes.findOneAsync(swimlaneId); + + const swimlane = Swimlanes.findOne(swimlaneId); if (!swimlane) { throw new Meteor.Error('swimlane-not-found', 'Swimlane not found'); } - - const board = await ReactiveCache.getBoard(swimlane.boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - + return swimlane.getOriginalPositionDescription(); }, /** * Get original position description for a list */ - async 'positionHistory.getListDescription'(listId) { + 'positionHistory.getListDescription'(listId) { check(listId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const list = await Lists.findOneAsync(listId); + + const list = Lists.findOne(listId); if (!list) { throw new Meteor.Error('list-not-found', 'List not found'); } - - const board = await ReactiveCache.getBoard(list.boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - + return list.getOriginalPositionDescription(); }, /** * Get original position description for a card */ - async 'positionHistory.getCardDescription'(cardId) { + 'positionHistory.getCardDescription'(cardId) { check(cardId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const card = await Cards.findOneAsync(cardId); + + const card = Cards.findOne(cardId); if (!card) { throw new Meteor.Error('card-not-found', 'Card not found'); } - - const board = await ReactiveCache.getBoard(card.boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - + return card.getOriginalPositionDescription(); }, /** * Get all position history for a board */ - async 'positionHistory.getBoardHistory'(boardId) { + 'positionHistory.getBoardHistory'(boardId) { check(boardId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const board = await ReactiveCache.getBoard(boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - + return PositionHistory.find({ boardId: boardId, }, { sort: { createdAt: -1 } - }).fetchAsync(); + }).fetch(); }, /** * Get position history by entity type for a board */ - async 'positionHistory.getBoardHistoryByType'(boardId, entityType) { + 'positionHistory.getBoardHistoryByType'(boardId, entityType) { check(boardId, String); check(entityType, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in.'); - } - - const board = await ReactiveCache.getBoard(boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - throw new Meteor.Error('not-authorized', 'You do not have access to this board.'); - } - + if (!['swimlane', 'list', 'card'].includes(entityType)) { throw new Meteor.Error('invalid-entity-type', 'Entity type must be swimlane, list, or card'); } - + return PositionHistory.find({ boardId: boardId, entityType: entityType, }, { sort: { createdAt: -1 } - }).fetchAsync(); + }).fetch(); }, }); diff --git a/server/migrations/comprehensiveBoardMigration.js b/server/migrations/comprehensiveBoardMigration.js index 59e90afc2..f9ea7c523 100644 --- a/server/migrations/comprehensiveBoardMigration.js +++ b/server/migrations/comprehensiveBoardMigration.js @@ -1,14 +1,14 @@ /** * Comprehensive Board Migration System - * + * * This migration handles all database structure changes from previous Wekan versions * to the current per-swimlane lists structure. It ensures: - * + * * 1. All cards are visible with proper swimlaneId and listId * 2. Lists are per-swimlane (no shared lists across swimlanes) * 3. No empty lists are created * 4. Handles various database structure versions from git history - * + * * Supported versions and their database structures: * - v7.94 and earlier: Shared lists across all swimlanes * - v8.00-v8.02: Transition period with mixed structures @@ -34,6 +34,7 @@ class ComprehensiveBoardMigration { 'fix_orphaned_cards', 'convert_shared_lists', 'ensure_per_swimlane_lists', + 'cleanup_empty_lists', 'validate_migration' ]; } @@ -41,9 +42,9 @@ class ComprehensiveBoardMigration { /** * Check if migration is needed for a board */ - async needsMigration(boardId) { + needsMigration(boardId) { try { - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (!board) return false; // Check if board has already been processed @@ -52,7 +53,7 @@ class ComprehensiveBoardMigration { } // Check for various issues that need migration - const issues = await this.detectMigrationIssues(boardId); + const issues = this.detectMigrationIssues(boardId); return issues.length > 0; } catch (error) { @@ -64,13 +65,13 @@ class ComprehensiveBoardMigration { /** * Detect all migration issues in a board */ - async detectMigrationIssues(boardId) { + detectMigrationIssues(boardId) { const issues = []; - + try { - const cards = await ReactiveCache.getCards({ boardId }); - const lists = await ReactiveCache.getLists({ boardId }); - const swimlanes = await ReactiveCache.getSwimlanes({ boardId }); + const cards = ReactiveCache.getCards({ boardId }); + const lists = ReactiveCache.getLists({ boardId }); + const swimlanes = ReactiveCache.getSwimlanes({ boardId }); // Issue 1: Cards with missing swimlaneId const cardsWithoutSwimlane = cards.filter(card => !card.swimlaneId); @@ -157,7 +158,7 @@ class ComprehensiveBoardMigration { console.log(`Starting comprehensive board migration for board ${boardId}`); } - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (!board) { throw new Error(`Board ${boardId} not found`); } @@ -168,6 +169,7 @@ class ComprehensiveBoardMigration { totalCardsProcessed: 0, totalListsProcessed: 0, totalListsCreated: 0, + totalListsRemoved: 0, errors: [] }; @@ -178,7 +180,7 @@ class ComprehensiveBoardMigration { const updateProgress = (stepName, stepProgress, stepStatus, stepDetails = null) => { currentStep++; const overallProgress = Math.round((currentStep / totalSteps) * 100); - + const progressData = { overallProgress, currentStep: currentStep, @@ -206,7 +208,7 @@ class ComprehensiveBoardMigration { issuesFound: results.steps.analyze.issueCount, needsMigration: results.steps.analyze.needsMigration }); - + // Step 2: Fix orphaned cards updateProgress('fix_orphaned_cards', 0, 'Fixing orphaned cards...'); results.steps.fixOrphanedCards = await this.fixOrphanedCards(boardId, (progress, status) => { @@ -237,7 +239,15 @@ class ComprehensiveBoardMigration { listsProcessed: results.steps.ensurePerSwimlane.listsProcessed }); - // Step 5: Validate migration + // Step 5: Cleanup empty lists + updateProgress('cleanup_empty_lists', 0, 'Cleaning up empty lists...'); + results.steps.cleanupEmpty = await this.cleanupEmptyLists(boardId); + results.totalListsRemoved += results.steps.cleanupEmpty.listsRemoved || 0; + updateProgress('cleanup_empty_lists', 100, 'Empty lists cleaned up', { + listsRemoved: results.steps.cleanupEmpty.listsRemoved + }); + + // Step 6: Validate migration updateProgress('validate_migration', 0, 'Validating migration...'); results.steps.validate = await this.validateMigration(boardId); updateProgress('validate_migration', 100, 'Migration validated', { @@ -246,16 +256,16 @@ class ComprehensiveBoardMigration { totalLists: results.steps.validate.totalLists }); - // Step 6: Fix avatar URLs + // Step 7: Fix avatar URLs updateProgress('fix_avatar_urls', 0, 'Fixing avatar URLs...'); - results.steps.fixAvatarUrls = await this.fixAvatarUrls(); + results.steps.fixAvatarUrls = await this.fixAvatarUrls(boardId); updateProgress('fix_avatar_urls', 100, 'Avatar URLs fixed', { avatarsFixed: results.steps.fixAvatarUrls.avatarsFixed }); // Step 8: Fix attachment URLs updateProgress('fix_attachment_urls', 0, 'Fixing attachment URLs...'); - results.steps.fixAttachmentUrls = await this.fixAttachmentUrls(); + results.steps.fixAttachmentUrls = await this.fixAttachmentUrls(boardId); updateProgress('fix_attachment_urls', 100, 'Attachment URLs fixed', { attachmentsFixed: results.steps.fixAttachmentUrls.attachmentsFixed }); @@ -288,7 +298,7 @@ class ComprehensiveBoardMigration { * Step 1: Analyze board structure */ async analyzeBoardStructure(boardId) { - const issues = await this.detectMigrationIssues(boardId); + const issues = this.detectMigrationIssues(boardId); return { issues, issueCount: issues.length, @@ -300,9 +310,9 @@ class ComprehensiveBoardMigration { * Step 2: Fix orphaned cards (cards with missing swimlaneId or listId) */ async fixOrphanedCards(boardId, progressCallback = null) { - const cards = await ReactiveCache.getCards({ boardId }); - const swimlanes = await ReactiveCache.getSwimlanes({ boardId }); - const lists = await ReactiveCache.getLists({ boardId }); + const cards = ReactiveCache.getCards({ boardId }); + const swimlanes = ReactiveCache.getSwimlanes({ boardId }); + const lists = ReactiveCache.getLists({ boardId }); let cardsFixed = 0; const defaultSwimlane = swimlanes.find(s => s.title === 'Default') || swimlanes[0]; @@ -323,7 +333,7 @@ class ComprehensiveBoardMigration { if (!card.listId) { // Find or create a default list for this swimlane const swimlaneId = updates.swimlaneId || card.swimlaneId; - let defaultList = lists.find(list => + let defaultList = lists.find(list => list.swimlaneId === swimlaneId && list.title === 'Default' ); @@ -370,9 +380,9 @@ class ComprehensiveBoardMigration { * Step 3: Convert shared lists to per-swimlane lists */ async convertSharedListsToPerSwimlane(boardId, progressCallback = null) { - const cards = await ReactiveCache.getCards({ boardId }); - const lists = await ReactiveCache.getLists({ boardId }); - const swimlanes = await ReactiveCache.getSwimlanes({ boardId }); + const cards = ReactiveCache.getCards({ boardId }); + const lists = ReactiveCache.getLists({ boardId }); + const swimlanes = ReactiveCache.getSwimlanes({ boardId }); let listsProcessed = 0; let listsCreated = 0; @@ -426,7 +436,7 @@ class ComprehensiveBoardMigration { // Check if we already have a list with the same title in this swimlane let targetList = existingLists.find(list => list.title === originalList.title); - + if (!targetList) { // Create a new list for this swimlane const newListData = { @@ -475,8 +485,8 @@ class ComprehensiveBoardMigration { * Step 4: Ensure all lists are per-swimlane */ async ensurePerSwimlaneLists(boardId) { - const lists = await ReactiveCache.getLists({ boardId }); - const swimlanes = await ReactiveCache.getSwimlanes({ boardId }); + const lists = ReactiveCache.getLists({ boardId }); + const swimlanes = ReactiveCache.getSwimlanes({ boardId }); const defaultSwimlane = swimlanes.find(s => s.title === 'Default') || swimlanes[0]; let listsProcessed = 0; @@ -501,19 +511,19 @@ class ComprehensiveBoardMigration { * Step 5: Cleanup empty lists (lists with no cards) */ async cleanupEmptyLists(boardId) { - const lists = await ReactiveCache.getLists({ boardId }); - const cards = await ReactiveCache.getCards({ boardId }); + const lists = ReactiveCache.getLists({ boardId }); + const cards = ReactiveCache.getCards({ boardId }); let listsRemoved = 0; for (const list of lists) { const listCards = cards.filter(card => card.listId === list._id); - + if (listCards.length === 0) { // Remove empty list Lists.remove(list._id); listsRemoved++; - + if (process.env.DEBUG === 'true') { console.log(`Removed empty list: ${list.title} (${list._id})`); } @@ -527,9 +537,9 @@ class ComprehensiveBoardMigration { * Step 6: Validate migration */ async validateMigration(boardId) { - const issues = await this.detectMigrationIssues(boardId); - const cards = await ReactiveCache.getCards({ boardId }); - const lists = await ReactiveCache.getLists({ boardId }); + const issues = this.detectMigrationIssues(boardId); + const cards = ReactiveCache.getCards({ boardId }); + const lists = ReactiveCache.getLists({ boardId }); // Check that all cards have valid swimlaneId and listId const validCards = cards.filter(card => card.swimlaneId && card.listId); @@ -554,8 +564,8 @@ class ComprehensiveBoardMigration { /** * Step 7: Fix avatar URLs (remove problematic auth parameters and fix URL formats) */ - async fixAvatarUrls() { - const users = await ReactiveCache.getUsers({}); + async fixAvatarUrls(boardId) { + const users = ReactiveCache.getUsers({}); let avatarsFixed = 0; for (const user of users) { @@ -563,7 +573,7 @@ class ComprehensiveBoardMigration { const avatarUrl = user.profile.avatarUrl; let needsUpdate = false; let cleanUrl = avatarUrl; - + // Check if URL has problematic parameters if (avatarUrl.includes('auth=false') || avatarUrl.includes('brokenIsFine=true')) { // Remove problematic parameters @@ -573,13 +583,13 @@ class ComprehensiveBoardMigration { cleanUrl = cleanUrl.replace(/\?$/g, ''); needsUpdate = true; } - + // Check if URL is using old CollectionFS format if (avatarUrl.includes('/cfs/files/avatars/')) { cleanUrl = cleanUrl.replace('/cfs/files/avatars/', '/cdn/storage/avatars/'); needsUpdate = true; } - + // Check if URL is missing the /cdn/storage/avatars/ prefix if (avatarUrl.includes('avatars/') && !avatarUrl.includes('/cdn/storage/avatars/') && !avatarUrl.includes('/cfs/files/avatars/')) { // This might be a relative URL, make it absolute @@ -588,7 +598,7 @@ class ComprehensiveBoardMigration { needsUpdate = true; } } - + if (needsUpdate) { // Update user's avatar URL Users.update(user._id, { @@ -597,7 +607,7 @@ class ComprehensiveBoardMigration { modifiedAt: new Date() } }); - + avatarsFixed++; } } @@ -609,8 +619,8 @@ class ComprehensiveBoardMigration { /** * Step 8: Fix attachment URLs (remove problematic auth parameters and fix URL formats) */ - async fixAttachmentUrls() { - const attachments = await ReactiveCache.getAttachments({}); + async fixAttachmentUrls(boardId) { + const attachments = ReactiveCache.getAttachments({}); let attachmentsFixed = 0; for (const attachment of attachments) { @@ -619,7 +629,7 @@ class ComprehensiveBoardMigration { const attachmentUrl = attachment.url; let needsUpdate = false; let cleanUrl = attachmentUrl; - + // Check if URL has problematic parameters if (attachmentUrl.includes('auth=false') || attachmentUrl.includes('brokenIsFine=true')) { // Remove problematic parameters @@ -629,26 +639,26 @@ class ComprehensiveBoardMigration { cleanUrl = cleanUrl.replace(/\?$/g, ''); needsUpdate = true; } - + // Check if URL is using old CollectionFS format if (attachmentUrl.includes('/cfs/files/attachments/')) { cleanUrl = cleanUrl.replace('/cfs/files/attachments/', '/cdn/storage/attachments/'); needsUpdate = true; } - + // Check if URL has /original/ path that should be removed if (attachmentUrl.includes('/original/')) { cleanUrl = cleanUrl.replace(/\/original\/[^\/\?#]+/, ''); needsUpdate = true; } - + // If we have a file ID, generate a universal URL const fileId = attachment._id; if (fileId && !isUniversalFileUrl(cleanUrl, 'attachment')) { cleanUrl = generateUniversalAttachmentUrl(fileId); needsUpdate = true; } - + if (needsUpdate) { // Update attachment URL Attachments.update(attachment._id, { @@ -657,7 +667,7 @@ class ComprehensiveBoardMigration { modifiedAt: new Date() } }); - + attachmentsFixed++; } } @@ -669,24 +679,24 @@ class ComprehensiveBoardMigration { /** * Get migration status for a board */ - async getMigrationStatus(boardId) { + getMigrationStatus(boardId) { try { - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (!board) { return { status: 'board_not_found' }; } if (board.comprehensiveMigrationCompleted) { - return { + return { status: 'completed', completedAt: board.comprehensiveMigrationCompletedAt, results: board.comprehensiveMigrationResults }; } - const needsMigration = await this.needsMigration(boardId); - const issues = await this.detectMigrationIssues(boardId); - + const needsMigration = this.needsMigration(boardId); + const issues = this.detectMigrationIssues(boardId); + return { status: needsMigration ? 'needed' : 'not_needed', issues, @@ -705,82 +715,53 @@ export const comprehensiveBoardMigration = new ComprehensiveBoardMigration(); // Meteor methods Meteor.methods({ - async 'comprehensiveBoardMigration.check'(boardId) { + 'comprehensiveBoardMigration.check'(boardId) { check(boardId, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - - return await comprehensiveBoardMigration.getMigrationStatus(boardId); + + return comprehensiveBoardMigration.getMigrationStatus(boardId); }, - async 'comprehensiveBoardMigration.execute'(boardId) { + 'comprehensiveBoardMigration.execute'(boardId) { check(boardId, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - - const user = await ReactiveCache.getUser(this.userId); - const board = await ReactiveCache.getBoard(boardId); - if (!board) { - throw new Meteor.Error('board-not-found'); - } - - const isBoardAdmin = board.hasAdmin(this.userId); - const isInstanceAdmin = user && user.isAdmin; - - if (!isBoardAdmin && !isInstanceAdmin) { - throw new Meteor.Error('not-authorized', 'You must be a board admin or instance admin to perform this action.'); - } - - return await comprehensiveBoardMigration.executeMigration(boardId); + + return comprehensiveBoardMigration.executeMigration(boardId); }, - async 'comprehensiveBoardMigration.needsMigration'(boardId) { + 'comprehensiveBoardMigration.needsMigration'(boardId) { check(boardId, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - - return await comprehensiveBoardMigration.needsMigration(boardId); + + return comprehensiveBoardMigration.needsMigration(boardId); }, - async 'comprehensiveBoardMigration.detectIssues'(boardId) { + 'comprehensiveBoardMigration.detectIssues'(boardId) { check(boardId, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - - return await comprehensiveBoardMigration.detectMigrationIssues(boardId); + + return comprehensiveBoardMigration.detectMigrationIssues(boardId); }, - async 'comprehensiveBoardMigration.fixAvatarUrls'() { + 'comprehensiveBoardMigration.fixAvatarUrls'(boardId) { + check(boardId, String); + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - - const user = await ReactiveCache.getUser(this.userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Only instance admins can perform this action.'); - } - - return await comprehensiveBoardMigration.fixAvatarUrls(); - }, - - async 'comprehensiveBoardMigration.fixAttachmentUrls'() { - if (!this.userId) { - throw new Meteor.Error('not-authorized'); - } - - const user = await ReactiveCache.getUser(this.userId); - if (!user || !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Only instance admins can perform this action.'); - } - - return await comprehensiveBoardMigration.fixAttachmentUrls(); + + return comprehensiveBoardMigration.fixAvatarUrls(boardId); } }); diff --git a/server/migrations/deleteDuplicateEmptyLists.js b/server/migrations/deleteDuplicateEmptyLists.js deleted file mode 100644 index f6b7fa91b..000000000 --- a/server/migrations/deleteDuplicateEmptyLists.js +++ /dev/null @@ -1,373 +0,0 @@ -/** - * Delete Duplicate Empty Lists Migration - * - * Safely deletes empty duplicate lists from a board: - * 1. First converts any shared lists to per-swimlane lists - * 2. Only deletes per-swimlane lists that: - * - Have no cards - * - Have another list with the same title on the same board that DOES have cards - * 3. This prevents deleting unique empty lists and only removes redundant duplicates - */ - -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; -import { ReactiveCache } from '/imports/reactiveCache'; -import Boards from '/models/boards'; -import Lists from '/models/lists'; -import Cards from '/models/cards'; -import Swimlanes from '/models/swimlanes'; - -class DeleteDuplicateEmptyListsMigration { - constructor() { - this.name = 'deleteDuplicateEmptyLists'; - this.version = 1; - } - - /** - * Check if migration is needed for a board - */ - async needsMigration(boardId) { - try { - const lists = await ReactiveCache.getLists({ boardId }); - const cards = await ReactiveCache.getCards({ boardId }); - - // Check if there are any empty lists that have a duplicate with the same title containing cards - for (const list of lists) { - // Skip shared lists - if (!list.swimlaneId || list.swimlaneId === '') { - continue; - } - - // Check if list is empty - const listCards = cards.filter(card => card.listId === list._id); - if (listCards.length === 0) { - // Check if there's a duplicate list with the same title that has cards - const duplicateListsWithSameTitle = lists.filter(l => - l._id !== list._id && - l.title === list.title && - l.boardId === boardId - ); - - for (const duplicateList of duplicateListsWithSameTitle) { - const duplicateListCards = cards.filter(card => card.listId === duplicateList._id); - if (duplicateListCards.length > 0) { - return true; // Found an empty list with a duplicate that has cards - } - } - } - } - - return false; - } catch (error) { - console.error('Error checking if deleteDuplicateEmptyLists migration is needed:', error); - return false; - } - } - - /** - * Execute the migration - */ - async executeMigration(boardId) { - try { - const results = { - sharedListsConverted: 0, - listsDeleted: 0, - errors: [] - }; - - // Step 1: Convert shared lists to per-swimlane lists first - const conversionResult = await this.convertSharedListsToPerSwimlane(boardId); - results.sharedListsConverted = conversionResult.listsConverted; - - // Step 2: Delete empty per-swimlane lists - const deletionResult = await this.deleteEmptyPerSwimlaneLists(boardId); - results.listsDeleted = deletionResult.listsDeleted; - - return { - success: true, - changes: [ - `Converted ${results.sharedListsConverted} shared lists to per-swimlane lists`, - `Deleted ${results.listsDeleted} empty per-swimlane lists` - ], - results - }; - } catch (error) { - console.error('Error executing deleteDuplicateEmptyLists migration:', error); - return { - success: false, - error: error.message - }; - } - } - - /** - * Convert shared lists (lists without swimlaneId) to per-swimlane lists - */ - async convertSharedListsToPerSwimlane(boardId) { - const lists = await ReactiveCache.getLists({ boardId }); - const swimlanes = await ReactiveCache.getSwimlanes({ boardId, archived: false }); - const cards = await ReactiveCache.getCards({ boardId }); - - let listsConverted = 0; - - // Find shared lists (lists without swimlaneId) - const sharedLists = lists.filter(list => !list.swimlaneId || list.swimlaneId === ''); - - if (sharedLists.length === 0) { - return { listsConverted: 0 }; - } - - for (const sharedList of sharedLists) { - // Get cards in this shared list - const listCards = cards.filter(card => card.listId === sharedList._id); - - // Group cards by swimlane - const cardsBySwimlane = {}; - for (const card of listCards) { - const swimlaneId = card.swimlaneId || 'default'; - if (!cardsBySwimlane[swimlaneId]) { - cardsBySwimlane[swimlaneId] = []; - } - cardsBySwimlane[swimlaneId].push(card); - } - - // Create per-swimlane lists for each swimlane that has cards - for (const swimlane of swimlanes) { - const swimlaneCards = cardsBySwimlane[swimlane._id] || []; - - if (swimlaneCards.length > 0) { - // Check if per-swimlane list already exists - const existingList = lists.find(l => - l.title === sharedList.title && - l.swimlaneId === swimlane._id && - l._id !== sharedList._id - ); - - if (!existingList) { - // Create new per-swimlane list - const newListId = Lists.insert({ - title: sharedList.title, - boardId: boardId, - swimlaneId: swimlane._id, - sort: sharedList.sort, - createdAt: new Date(), - updatedAt: new Date(), - archived: false - }); - - // Move cards to the new list - for (const card of swimlaneCards) { - Cards.update(card._id, { - $set: { - listId: newListId, - swimlaneId: swimlane._id - } - }); - } - - if (process.env.DEBUG === 'true') { - console.log(`Created per-swimlane list "${sharedList.title}" for swimlane ${swimlane.title || swimlane._id}`); - } - } else { - // Move cards to existing per-swimlane list - for (const card of swimlaneCards) { - Cards.update(card._id, { - $set: { - listId: existingList._id, - swimlaneId: swimlane._id - } - }); - } - - if (process.env.DEBUG === 'true') { - console.log(`Moved cards to existing per-swimlane list "${sharedList.title}" in swimlane ${swimlane.title || swimlane._id}`); - } - } - } - } - - // Remove the shared list (now that all cards are moved) - Lists.remove(sharedList._id); - listsConverted++; - - if (process.env.DEBUG === 'true') { - console.log(`Removed shared list "${sharedList.title}"`); - } - } - - return { listsConverted }; - } - - /** - * Delete empty per-swimlane lists - * Only deletes lists that: - * 1. Have a swimlaneId (are per-swimlane, not shared) - * 2. Have no cards - * 3. Have a duplicate list with the same title on the same board that contains cards - */ - async deleteEmptyPerSwimlaneLists(boardId) { - const lists = await ReactiveCache.getLists({ boardId }); - const cards = await ReactiveCache.getCards({ boardId }); - - let listsDeleted = 0; - - for (const list of lists) { - // Safety check 1: List must have a swimlaneId (must be per-swimlane, not shared) - if (!list.swimlaneId || list.swimlaneId === '') { - if (process.env.DEBUG === 'true') { - console.log(`Skipping list "${list.title}" - no swimlaneId (shared list)`); - } - continue; - } - - // Safety check 2: List must have no cards - const listCards = cards.filter(card => card.listId === list._id); - if (listCards.length > 0) { - if (process.env.DEBUG === 'true') { - console.log(`Skipping list "${list.title}" - has ${listCards.length} cards`); - } - continue; - } - - // Safety check 3: There must be another list with the same title on the same board that has cards - const duplicateListsWithSameTitle = lists.filter(l => - l._id !== list._id && - l.title === list.title && - l.boardId === boardId - ); - - let hasDuplicateWithCards = false; - for (const duplicateList of duplicateListsWithSameTitle) { - const duplicateListCards = cards.filter(card => card.listId === duplicateList._id); - if (duplicateListCards.length > 0) { - hasDuplicateWithCards = true; - break; - } - } - - if (!hasDuplicateWithCards) { - if (process.env.DEBUG === 'true') { - console.log(`Skipping list "${list.title}" - no duplicate list with same title that has cards`); - } - continue; - } - - // All safety checks passed - delete the empty per-swimlane list - Lists.remove(list._id); - listsDeleted++; - - if (process.env.DEBUG === 'true') { - console.log(`Deleted empty per-swimlane list: "${list.title}" (swimlane: ${list.swimlaneId}) - duplicate with cards exists`); - } - } - - return { listsDeleted }; - } - - /** - * Get detailed status of empty lists - */ - async getStatus(boardId) { - const lists = await ReactiveCache.getLists({ boardId }); - const cards = await ReactiveCache.getCards({ boardId }); - - const sharedLists = []; - const emptyPerSwimlaneLists = []; - const nonEmptyLists = []; - - for (const list of lists) { - const listCards = cards.filter(card => card.listId === list._id); - const isShared = !list.swimlaneId || list.swimlaneId === ''; - const isEmpty = listCards.length === 0; - - if (isShared) { - sharedLists.push({ - id: list._id, - title: list.title, - cardCount: listCards.length - }); - } else if (isEmpty) { - emptyPerSwimlaneLists.push({ - id: list._id, - title: list.title, - swimlaneId: list.swimlaneId - }); - } else { - nonEmptyLists.push({ - id: list._id, - title: list.title, - swimlaneId: list.swimlaneId, - cardCount: listCards.length - }); - } - } - - return { - sharedListsCount: sharedLists.length, - emptyPerSwimlaneLists: emptyPerSwimlaneLists.length, - totalLists: lists.length, - details: { - sharedLists, - emptyPerSwimlaneLists, - nonEmptyLists - } - }; - } -} - -const deleteDuplicateEmptyListsMigration = new DeleteDuplicateEmptyListsMigration(); - -// Register Meteor methods -Meteor.methods({ - async 'deleteDuplicateEmptyLists.needsMigration'(boardId) { - check(boardId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in'); - } - - return await deleteDuplicateEmptyListsMigration.needsMigration(boardId); - }, - - async 'deleteDuplicateEmptyLists.execute'(boardId) { - check(boardId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in'); - } - - // Check if user is board admin - const board = await ReactiveCache.getBoard(boardId); - if (!board) { - throw new Meteor.Error('board-not-found', 'Board not found'); - } - - const user = await ReactiveCache.getUser(this.userId); - if (!user) { - throw new Meteor.Error('user-not-found', 'User not found'); - } - - // Only board admins can run migrations - const isBoardAdmin = board.members && board.members.some( - member => member.userId === this.userId && member.isAdmin - ); - - if (!isBoardAdmin && !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Only board administrators can run migrations'); - } - - return await deleteDuplicateEmptyListsMigration.executeMigration(boardId); - }, - - async 'deleteDuplicateEmptyLists.getStatus'(boardId) { - check(boardId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in'); - } - - return await deleteDuplicateEmptyListsMigration.getStatus(boardId); - } -}); - -export default deleteDuplicateEmptyListsMigration; diff --git a/server/migrations/ensureValidSwimlaneIds.js b/server/migrations/ensureValidSwimlaneIds.js deleted file mode 100644 index 31e0af281..000000000 --- a/server/migrations/ensureValidSwimlaneIds.js +++ /dev/null @@ -1,361 +0,0 @@ -/** - * Migration: Ensure all entities have valid swimlaneId - * - * This migration ensures that: - * 1. All cards have a valid swimlaneId - * 2. All lists have a valid swimlaneId (if applicable) - * 3. Orphaned entities (without valid swimlaneId) are moved to a "Rescued Data" swimlane - * - * This is similar to the existing rescue migration but specifically for swimlaneId validation - */ - -// Helper collection to track migrations - must be defined first -const Migrations = new Mongo.Collection('migrations'); - -// DISABLED: This migration now runs from Admin Panel / Cron / Run All Migrations -// Instead of running automatically on startup -/* -Meteor.startup(() => { - // Only run on server - if (!Meteor.isServer) return; - - const MIGRATION_NAME = 'ensure-valid-swimlane-ids'; - const MIGRATION_VERSION = 1; - - // Check if migration already ran - const existingMigration = Migrations.findOne({ name: MIGRATION_NAME }); - if (existingMigration && existingMigration.version >= MIGRATION_VERSION) { - return; - } - - console.log(`Running migration: ${MIGRATION_NAME} v${MIGRATION_VERSION}`); -*/ - -// Export migration functions for use by cron migration manager -export const MIGRATION_NAME = 'ensure-valid-swimlane-ids'; -export const MIGRATION_VERSION = 1; - -/** - * Get or create a "Rescued Data" swimlane for a board - */ -function getOrCreateRescuedSwimlane(boardId) { - const board = Boards.findOne(boardId); - if (!board) return null; - - // Look for existing rescued data swimlane - let rescuedSwimlane = Swimlanes.findOne({ - boardId, - title: { $regex: /rescued.*data/i }, - }); - - if (!rescuedSwimlane) { - // Create a new rescued data swimlane - const swimlaneId = Swimlanes.insert({ - title: 'Rescued Data (Missing Swimlane)', - boardId, - archived: false, - sort: 9999999, // Put at the end - type: 'swimlane', - color: 'red', - }); - - rescuedSwimlane = Swimlanes.findOne(swimlaneId); - - Activities.insert({ - userId: 'migration', - type: 'swimlane', - activityType: 'createSwimlane', - boardId, - swimlaneId, - title: 'Created rescued data swimlane during migration', - }); - } - - return rescuedSwimlane; - } - - /** - * Validate and fix cards without valid swimlaneId - */ - function fixCardsWithoutSwimlaneId() { - let fixedCount = 0; - let rescuedCount = 0; - - const cardsWithoutSwimlane = Cards.find({ - $or: [ - { swimlaneId: { $exists: false } }, - { swimlaneId: null }, - { swimlaneId: '' }, - ], - }).fetch(); - - console.log(`Found ${cardsWithoutSwimlane.length} cards without swimlaneId`); - - cardsWithoutSwimlane.forEach(card => { - const board = Boards.findOne(card.boardId); - if (!board) { - console.warn(`Card ${card._id} has invalid boardId: ${card.boardId}`); - return; - } - - // Try to get default swimlane - let defaultSwimlane = Swimlanes.findOne({ - boardId: card.boardId, - type: { $ne: 'template-swimlane' }, - archived: false, - }, { sort: { sort: 1 } }); - - if (!defaultSwimlane) { - // No swimlanes at all - create default - const swimlaneId = Swimlanes.insert({ - title: 'Default', - boardId: card.boardId, - archived: false, - sort: 0, - type: 'swimlane', - }); - defaultSwimlane = Swimlanes.findOne(swimlaneId); - } - - if (defaultSwimlane) { - Cards.update(card._id, { - $set: { swimlaneId: defaultSwimlane._id }, - }); - fixedCount++; - } else { - console.warn(`Could not find or create default swimlane for card ${card._id}`); - } - }); - - return { fixedCount, rescuedCount }; - } - - /** - * Validate and fix lists without valid swimlaneId - */ - function fixListsWithoutSwimlaneId() { - let fixedCount = 0; - - const listsWithoutSwimlane = Lists.find({ - $or: [ - { swimlaneId: { $exists: false } }, - { swimlaneId: null }, - ], - }).fetch(); - - console.log(`Found ${listsWithoutSwimlane.length} lists without swimlaneId`); - - listsWithoutSwimlane.forEach(list => { - // Set to empty string for backward compatibility - // (lists can be shared across swimlanes) - Lists.update(list._id, { - $set: { swimlaneId: '' }, - }); - fixedCount++; - }); - - return { fixedCount }; - } - - /** - * Find and rescue orphaned cards (swimlaneId points to non-existent swimlane) - */ - function rescueOrphanedCards() { - let rescuedCount = 0; - - const allCards = Cards.find({}).fetch(); - - allCards.forEach(card => { - if (!card.swimlaneId) return; // Handled by fixCardsWithoutSwimlaneId - - // Check if swimlane exists - const swimlane = Swimlanes.findOne(card.swimlaneId); - if (!swimlane) { - // Orphaned card - swimlane doesn't exist - const rescuedSwimlane = getOrCreateRescuedSwimlane(card.boardId); - - if (rescuedSwimlane) { - Cards.update(card._id, { - $set: { swimlaneId: rescuedSwimlane._id }, - }); - rescuedCount++; - - Activities.insert({ - userId: 'migration', - type: 'card', - activityType: 'moveCard', - boardId: card.boardId, - cardId: card._id, - swimlaneId: rescuedSwimlane._id, - listId: card.listId, - title: `Rescued card from deleted swimlane`, - }); - } - } - }); - - return { rescuedCount }; - } - - /** - * Ensure all swimlaneId references are always saved in all operations - * This adds a global hook to validate swimlaneId before insert/update - */ - function addSwimlaneIdValidationHooks() { - // Card insert hook - Cards.before.insert(function(userId, doc) { - if (!doc.swimlaneId) { - const board = Boards.findOne(doc.boardId); - if (board) { - const defaultSwimlane = Swimlanes.findOne({ - boardId: doc.boardId, - type: { $ne: 'template-swimlane' }, - archived: false, - }, { sort: { sort: 1 } }); - - if (defaultSwimlane) { - doc.swimlaneId = defaultSwimlane._id; - } else { - console.warn('No default swimlane found for new card, creating one'); - const swimlaneId = Swimlanes.insert({ - title: 'Default', - boardId: doc.boardId, - archived: false, - sort: 0, - type: 'swimlane', - }); - doc.swimlaneId = swimlaneId; - } - } - } - }); - - // Card update hook - ensure swimlaneId is never removed - Cards.before.update(function(userId, doc, fieldNames, modifier) { - if (modifier.$unset && modifier.$unset.swimlaneId) { - delete modifier.$unset.swimlaneId; - console.warn('Prevented removal of swimlaneId from card', doc._id); - } - - if (modifier.$set && modifier.$set.swimlaneId === null) { - const defaultSwimlane = Swimlanes.findOne({ - boardId: doc.boardId, - type: { $ne: 'template-swimlane' }, - archived: false, - }, { sort: { sort: 1 } }); - - if (defaultSwimlane) { - modifier.$set.swimlaneId = defaultSwimlane._id; - } - } - }); - } - - // Exported function to run the migration from cron - export function runEnsureValidSwimlaneIdsMigration() { - const existingMigration = Migrations.findOne({ name: MIGRATION_NAME }); - if (existingMigration && existingMigration.version >= MIGRATION_VERSION) { - console.log(`Migration ${MIGRATION_NAME} already completed`); - return { alreadyCompleted: true, ...existingMigration.results }; - } - - console.log(`Running migration: ${MIGRATION_NAME} v${MIGRATION_VERSION}`); - - try { - // Run all fix operations - const cardResults = fixCardsWithoutSwimlaneId(); - const listResults = fixListsWithoutSwimlaneId(); - const rescueResults = rescueOrphanedCards(); - - console.log('Migration results:'); - console.log(`- Fixed ${cardResults.fixedCount} cards without swimlaneId`); - console.log(`- Fixed ${listResults.fixedCount} lists without swimlaneId`); - console.log(`- Rescued ${rescueResults.rescuedCount} orphaned cards`); - - // Record migration completion - Migrations.upsert( - { name: MIGRATION_NAME }, - { - $set: { - name: MIGRATION_NAME, - version: MIGRATION_VERSION, - completedAt: new Date(), - results: { - cardsFixed: cardResults.fixedCount, - listsFixed: listResults.fixedCount, - cardsRescued: rescueResults.rescuedCount, - }, - }, - } - ); - - console.log(`Migration ${MIGRATION_NAME} completed successfully`); - - return { - success: true, - cardsFixed: cardResults.fixedCount, - listsFixed: listResults.fixedCount, - cardsRescued: rescueResults.rescuedCount, - }; - } catch (error) { - console.error(`Migration ${MIGRATION_NAME} failed:`, error); - throw error; - } - } - -// Install validation hooks on startup (always run these for data integrity) -Meteor.startup(() => { - if (!Meteor.isServer) return; - - try { - addSwimlaneIdValidationHooks(); - console.log('SwimlaneId validation hooks installed'); - } catch (error) { - console.error('Failed to install swimlaneId validation hooks:', error); - } -}); - -/* - // OLD AUTO-RUN CODE - DISABLED - try { - // Run all fix operations - const cardResults = fixCardsWithoutSwimlaneId(); - const listResults = fixListsWithoutSwimlaneId(); - const rescueResults = rescueOrphanedCards(); - - console.log('Migration results:'); - console.log(`- Fixed ${cardResults.fixedCount} cards without swimlaneId`); - console.log(`- Fixed ${listResults.fixedCount} lists without swimlaneId`); - console.log(`- Rescued ${rescueResults.rescuedCount} orphaned cards`); - - // Record migration completion - Migrations.upsert( - { name: MIGRATION_NAME }, - { - $set: { - name: MIGRATION_NAME, - version: MIGRATION_VERSION, - completedAt: new Date(), - results: { - cardsFixed: cardResults.fixedCount, - listsFixed: listResults.fixedCount, - cardsRescued: rescueResults.rescuedCount, - }, - }, - } - ); - - console.log(`Migration ${MIGRATION_NAME} completed successfully`); - } catch (error) { - console.error(`Migration ${MIGRATION_NAME} failed:`, error); - } - - // Add validation hooks (outside try-catch to ensure they run even if migration failed) - try { - addSwimlaneIdValidationHooks(); - console.log('SwimlaneId validation hooks installed'); - } catch (error) { - console.error('Failed to install swimlaneId validation hooks:', error); - } -}); -*/ diff --git a/server/migrations/fixAllFileUrls.js b/server/migrations/fixAllFileUrls.js index f577125ec..caba86e68 100644 --- a/server/migrations/fixAllFileUrls.js +++ b/server/migrations/fixAllFileUrls.js @@ -3,14 +3,10 @@ * Ensures all attachment and avatar URLs are universal and work regardless of ROOT_URL and PORT settings */ -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; import { ReactiveCache } from '/imports/reactiveCache'; -import Boards from '/models/boards'; import Users from '/models/users'; import Attachments from '/models/attachments'; import Avatars from '/models/avatars'; -import Cards from '/models/cards'; import { generateUniversalAttachmentUrl, generateUniversalAvatarUrl, cleanFileUrl, extractFileIdFromUrl, isUniversalFileUrl } from '/models/lib/universalUrlGenerator'; class FixAllFileUrlsMigration { @@ -20,19 +16,11 @@ class FixAllFileUrlsMigration { } /** - * Check if migration is needed for a board + * Check if migration is needed */ - async needsMigration(boardId) { - // Get all users who are members of this board - const board = await ReactiveCache.getBoard(boardId); - if (!board || !board.members) { - return false; - } - - const memberIds = board.members.map(m => m.userId); - - // Check for problematic avatar URLs for board members - const users = await ReactiveCache.getUsers({ _id: { $in: memberIds } }); + needsMigration() { + // Check for problematic avatar URLs + const users = ReactiveCache.getUsers({}); for (const user of users) { if (user.profile && user.profile.avatarUrl) { const avatarUrl = user.profile.avatarUrl; @@ -42,14 +30,15 @@ class FixAllFileUrlsMigration { } } - // Check for problematic attachment URLs on this board - const cards = await ReactiveCache.getCards({ boardId }); - const cardIds = cards.map(c => c._id); - const attachments = await ReactiveCache.getAttachments({ cardId: { $in: cardIds } }); - - for (const attachment of attachments) { - if (attachment.url && this.hasProblematicUrl(attachment.url)) { - return true; + // Check for problematic attachment URLs in cards + const cards = ReactiveCache.getCards({}); + for (const card of cards) { + if (card.attachments) { + for (const attachment of card.attachments) { + if (attachment.url && this.hasProblematicUrl(attachment.url)) { + return true; + } + } } } @@ -61,17 +50,17 @@ class FixAllFileUrlsMigration { */ hasProblematicUrl(url) { if (!url) return false; - + // Check for auth parameters if (url.includes('auth=false') || url.includes('brokenIsFine=true')) { return true; } - + // Check for absolute URLs with domains if (url.startsWith('http://') || url.startsWith('https://')) { return true; } - + // Check for ROOT_URL dependencies if (Meteor.isServer && process.env.ROOT_URL) { try { @@ -83,74 +72,67 @@ class FixAllFileUrlsMigration { // Ignore URL parsing errors } } - + // Check for non-universal file URLs if (url.includes('/cfs/files/') && !isUniversalFileUrl(url, 'attachment') && !isUniversalFileUrl(url, 'avatar')) { return true; } - + return false; } /** - * Execute the migration for a board + * Execute the migration */ - async execute(boardId) { + async execute() { let filesFixed = 0; let errors = []; - console.log(`Starting universal file URL migration for board ${boardId}...`); + console.log(`Starting universal file URL migration...`); try { - // Fix avatar URLs for board members - const avatarFixed = await this.fixAvatarUrls(boardId); + // Fix avatar URLs + const avatarFixed = await this.fixAvatarUrls(); filesFixed += avatarFixed; - // Fix attachment URLs for board cards - const attachmentFixed = await this.fixAttachmentUrls(boardId); + // Fix attachment URLs + const attachmentFixed = await this.fixAttachmentUrls(); filesFixed += attachmentFixed; // Fix card attachment references - const cardFixed = await this.fixCardAttachmentUrls(boardId); + const cardFixed = await this.fixCardAttachmentUrls(); filesFixed += cardFixed; } catch (error) { - console.error('Error during file URL migration for board', boardId, ':', error); + console.error('Error during file URL migration:', error); errors.push(error.message); } - console.log(`Universal file URL migration completed for board ${boardId}. Fixed ${filesFixed} file URLs.`); - + console.log(`Universal file URL migration completed. Fixed ${filesFixed} file URLs.`); + return { success: errors.length === 0, filesFixed, - errors, - changes: [`Fixed ${filesFixed} file URLs for this board`] + errors }; } /** - * Fix avatar URLs in user profiles for board members + * Fix avatar URLs in user profiles */ - async fixAvatarUrls(boardId) { - const board = await ReactiveCache.getBoard(boardId); - if (!board || !board.members) { - return 0; - } - - const memberIds = board.members.map(m => m.userId); - const users = await ReactiveCache.getUsers({ _id: { $in: memberIds } }); + async fixAvatarUrls() { + const users = ReactiveCache.getUsers({}); let avatarsFixed = 0; for (const user of users) { if (user.profile && user.profile.avatarUrl) { const avatarUrl = user.profile.avatarUrl; - + if (this.hasProblematicUrl(avatarUrl)) { try { // Extract file ID from URL const fileId = extractFileIdFromUrl(avatarUrl, 'avatar'); - + let cleanUrl; if (fileId) { // Generate universal URL @@ -159,7 +141,7 @@ class FixAllFileUrlsMigration { // Clean existing URL cleanUrl = cleanFileUrl(avatarUrl, 'avatar'); } - + if (cleanUrl && cleanUrl !== avatarUrl) { // Update user's avatar URL Users.update(user._id, { @@ -168,9 +150,9 @@ class FixAllFileUrlsMigration { modifiedAt: new Date() } }); - + avatarsFixed++; - + if (process.env.DEBUG === 'true') { console.log(`Fixed avatar URL for user ${user.username}: ${avatarUrl} -> ${cleanUrl}`); } @@ -186,12 +168,10 @@ class FixAllFileUrlsMigration { } /** - * Fix attachment URLs in attachment records for this board + * Fix attachment URLs in attachment records */ - async fixAttachmentUrls(boardId) { - const cards = await ReactiveCache.getCards({ boardId }); - const cardIds = cards.map(c => c._id); - const attachments = await ReactiveCache.getAttachments({ cardId: { $in: cardIds } }); + async fixAttachmentUrls() { + const attachments = ReactiveCache.getAttachments({}); let attachmentsFixed = 0; for (const attachment of attachments) { @@ -200,7 +180,7 @@ class FixAllFileUrlsMigration { try { const fileId = attachment._id; const cleanUrl = generateUniversalAttachmentUrl(fileId); - + if (cleanUrl && cleanUrl !== attachment.url) { // Update attachment URL Attachments.update(attachment._id, { @@ -209,9 +189,9 @@ class FixAllFileUrlsMigration { modifiedAt: new Date() } }); - + attachmentsFixed++; - + if (process.env.DEBUG === 'true') { console.log(`Fixed attachment URL: ${attachment.url} -> ${cleanUrl}`); } @@ -226,42 +206,51 @@ class FixAllFileUrlsMigration { } /** - * Fix attachment URLs in the Attachments collection for this board + * Fix attachment URLs in card references */ - async fixCardAttachmentUrls(boardId) { - const cards = await ReactiveCache.getCards({ boardId }); - const cardIds = cards.map(c => c._id); - const attachments = await ReactiveCache.getAttachments({ cardId: { $in: cardIds } }); - let attachmentsFixed = 0; + async fixCardAttachmentUrls() { + const cards = ReactiveCache.getCards({}); + let cardsFixed = 0; - for (const attachment of attachments) { - if (attachment.url && this.hasProblematicUrl(attachment.url)) { - try { - const fileId = attachment._id || extractFileIdFromUrl(attachment.url, 'attachment'); - const cleanUrl = fileId ? generateUniversalAttachmentUrl(fileId) : cleanFileUrl(attachment.url, 'attachment'); - - if (cleanUrl && cleanUrl !== attachment.url) { - // Update attachment with fixed URL - Attachments.update(attachment._id, { - $set: { - url: cleanUrl, - modifiedAt: new Date() + for (const card of cards) { + if (card.attachments) { + let needsUpdate = false; + const updatedAttachments = card.attachments.map(attachment => { + if (attachment.url && this.hasProblematicUrl(attachment.url)) { + try { + const fileId = attachment._id || extractFileIdFromUrl(attachment.url, 'attachment'); + const cleanUrl = fileId ? generateUniversalAttachmentUrl(fileId) : cleanFileUrl(attachment.url, 'attachment'); + + if (cleanUrl && cleanUrl !== attachment.url) { + needsUpdate = true; + return { ...attachment, url: cleanUrl }; } - }); - - attachmentsFixed++; - - if (process.env.DEBUG === 'true') { - console.log(`Fixed attachment URL ${attachment._id}`); + } catch (error) { + console.error(`Error fixing card attachment URL:`, error); } } - } catch (error) { - console.error(`Error fixing attachment URL:`, error); + return attachment; + }); + + if (needsUpdate) { + // Update card with fixed attachment URLs + Cards.update(card._id, { + $set: { + attachments: updatedAttachments, + modifiedAt: new Date() + } + }); + + cardsFixed++; + + if (process.env.DEBUG === 'true') { + console.log(`Fixed attachment URLs in card ${card._id}`); + } } } } - return attachmentsFixed; + return cardsFixed; } } @@ -270,43 +259,19 @@ export const fixAllFileUrlsMigration = new FixAllFileUrlsMigration(); // Meteor methods Meteor.methods({ - async 'fixAllFileUrls.execute'(boardId) { - check(boardId, String); - + 'fixAllFileUrls.execute'() { if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in'); + throw new Meteor.Error('not-authorized'); } - - // Check if user is board admin - const board = await ReactiveCache.getBoard(boardId); - if (!board) { - throw new Meteor.Error('board-not-found', 'Board not found'); - } - - const user = await ReactiveCache.getUser(this.userId); - if (!user) { - throw new Meteor.Error('user-not-found', 'User not found'); - } - - // Only board admins can run migrations - const isBoardAdmin = board.members && board.members.some( - member => member.userId === this.userId && member.isAdmin - ); - - if (!isBoardAdmin && !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Only board administrators can run migrations'); - } - - return await fixAllFileUrlsMigration.execute(boardId); + + return fixAllFileUrlsMigration.execute(); }, - async 'fixAllFileUrls.needsMigration'(boardId) { - check(boardId, String); - + 'fixAllFileUrls.needsMigration'() { if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in'); + throw new Meteor.Error('not-authorized'); } - - return await fixAllFileUrlsMigration.needsMigration(boardId); + + return fixAllFileUrlsMigration.needsMigration(); } }); diff --git a/server/migrations/fixAvatarUrls.js b/server/migrations/fixAvatarUrls.js index 445dec945..f542903ed 100644 --- a/server/migrations/fixAvatarUrls.js +++ b/server/migrations/fixAvatarUrls.js @@ -3,10 +3,7 @@ * Removes problematic auth parameters from existing avatar URLs */ -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; import { ReactiveCache } from '/imports/reactiveCache'; -import Boards from '/models/boards'; import Users from '/models/users'; import { generateUniversalAvatarUrl, cleanFileUrl, extractFileIdFromUrl, isUniversalFileUrl } from '/models/lib/universalUrlGenerator'; @@ -17,17 +14,10 @@ class FixAvatarUrlsMigration { } /** - * Check if migration is needed for a board + * Check if migration is needed */ - async needsMigration(boardId) { - // Get all users who are members of this board - const board = await ReactiveCache.getBoard(boardId); - if (!board || !board.members) { - return false; - } - - const memberIds = board.members.map(m => m.userId); - const users = await ReactiveCache.getUsers({ _id: { $in: memberIds } }); + needsMigration() { + const users = ReactiveCache.getUsers({}); for (const user of users) { if (user.profile && user.profile.avatarUrl) { @@ -37,35 +27,25 @@ class FixAvatarUrlsMigration { } } } - + return false; } /** - * Execute the migration for a board + * Execute the migration */ - async execute(boardId) { - // Get all users who are members of this board - const board = await ReactiveCache.getBoard(boardId); - if (!board || !board.members) { - return { - success: false, - error: 'Board not found or has no members' - }; - } - - const memberIds = board.members.map(m => m.userId); - const users = await ReactiveCache.getUsers({ _id: { $in: memberIds } }); + async execute() { + const users = ReactiveCache.getUsers({}); let avatarsFixed = 0; - console.log(`Starting avatar URL fix migration for board ${boardId}...`); + console.log(`Starting avatar URL fix migration...`); for (const user of users) { if (user.profile && user.profile.avatarUrl) { const avatarUrl = user.profile.avatarUrl; let needsUpdate = false; let cleanUrl = avatarUrl; - + // Check if URL has problematic parameters if (avatarUrl.includes('auth=false') || avatarUrl.includes('brokenIsFine=true')) { // Remove problematic parameters @@ -75,13 +55,13 @@ class FixAvatarUrlsMigration { cleanUrl = cleanUrl.replace(/\?$/g, ''); needsUpdate = true; } - + // Check if URL is using old CollectionFS format if (avatarUrl.includes('/cfs/files/avatars/')) { cleanUrl = cleanUrl.replace('/cfs/files/avatars/', '/cdn/storage/avatars/'); needsUpdate = true; } - + // Check if URL is missing the /cdn/storage/avatars/ prefix if (avatarUrl.includes('avatars/') && !avatarUrl.includes('/cdn/storage/avatars/') && !avatarUrl.includes('/cfs/files/avatars/')) { // This might be a relative URL, make it absolute @@ -90,14 +70,14 @@ class FixAvatarUrlsMigration { needsUpdate = true; } } - + // If we have a file ID, generate a universal URL const fileId = extractFileIdFromUrl(avatarUrl, 'avatar'); if (fileId && !isUniversalFileUrl(cleanUrl, 'avatar')) { cleanUrl = generateUniversalAvatarUrl(fileId); needsUpdate = true; } - + if (needsUpdate) { // Update user's avatar URL Users.update(user._id, { @@ -106,9 +86,9 @@ class FixAvatarUrlsMigration { modifiedAt: new Date() } }); - + avatarsFixed++; - + if (process.env.DEBUG === 'true') { console.log(`Fixed avatar URL for user ${user.username}: ${avatarUrl} -> ${cleanUrl}`); } @@ -116,12 +96,11 @@ class FixAvatarUrlsMigration { } } - console.log(`Avatar URL fix migration completed for board ${boardId}. Fixed ${avatarsFixed} avatar URLs.`); - + console.log(`Avatar URL fix migration completed. Fixed ${avatarsFixed} avatar URLs.`); + return { success: true, - avatarsFixed, - changes: [`Fixed ${avatarsFixed} avatar URLs for board members`] + avatarsFixed }; } } @@ -131,43 +110,19 @@ export const fixAvatarUrlsMigration = new FixAvatarUrlsMigration(); // Meteor method Meteor.methods({ - async 'fixAvatarUrls.execute'(boardId) { - check(boardId, String); - + 'fixAvatarUrls.execute'() { if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in'); + throw new Meteor.Error('not-authorized'); } - - // Check if user is board admin - const board = await ReactiveCache.getBoard(boardId); - if (!board) { - throw new Meteor.Error('board-not-found', 'Board not found'); - } - - const user = await ReactiveCache.getUser(this.userId); - if (!user) { - throw new Meteor.Error('user-not-found', 'User not found'); - } - - // Only board admins can run migrations - const isBoardAdmin = board.members && board.members.some( - member => member.userId === this.userId && member.isAdmin - ); - - if (!isBoardAdmin && !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Only board administrators can run migrations'); - } - - return await fixAvatarUrlsMigration.execute(boardId); + + return fixAvatarUrlsMigration.execute(); }, - async 'fixAvatarUrls.needsMigration'(boardId) { - check(boardId, String); - + 'fixAvatarUrls.needsMigration'() { if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in'); + throw new Meteor.Error('not-authorized'); } - - return await fixAvatarUrlsMigration.needsMigration(boardId); + + return fixAvatarUrlsMigration.needsMigration(); } }); diff --git a/server/migrations/fixMissingListsMigration.js b/server/migrations/fixMissingListsMigration.js index dd2af5dbd..22e5b16de 100644 --- a/server/migrations/fixMissingListsMigration.js +++ b/server/migrations/fixMissingListsMigration.js @@ -1,17 +1,17 @@ /** * Fix Missing Lists Migration - * + * * This migration fixes the issue where cards have incorrect listId references * due to the per-swimlane lists change. It detects cards with mismatched * listId/swimlaneId and creates the missing lists. - * + * * Issue: When upgrading from v7.94 to v8.02, cards that were in different * swimlanes but shared the same list now have wrong listId references. - * + * * Example: * - Card1: listId: 'HB93dWNnY5bgYdtxc', swimlaneId: 'sK69SseWkh3tMbJvg' * - Card2: listId: 'HB93dWNnY5bgYdtxc', swimlaneId: 'XeecF9nZxGph4zcT4' - * + * * Card2 should have a different listId that corresponds to its swimlane. */ @@ -31,9 +31,9 @@ class FixMissingListsMigration { /** * Check if migration is needed for a board */ - async needsMigration(boardId) { + needsMigration(boardId) { try { - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (!board) return false; // Check if board has already been processed @@ -42,8 +42,8 @@ class FixMissingListsMigration { } // Check if there are cards with mismatched listId/swimlaneId - const cards = await ReactiveCache.getCards({ boardId }); - const lists = await ReactiveCache.getLists({ boardId }); + const cards = ReactiveCache.getCards({ boardId }); + const lists = ReactiveCache.getLists({ boardId }); // Create a map of listId -> swimlaneId for existing lists const listSwimlaneMap = new Map(); @@ -77,20 +77,20 @@ class FixMissingListsMigration { if (process.env.DEBUG === 'true') { console.log(`Starting fix missing lists migration for board ${boardId}`); } - - const board = await ReactiveCache.getBoard(boardId); + + const board = ReactiveCache.getBoard(boardId); if (!board) { throw new Error(`Board ${boardId} not found`); } - const cards = await ReactiveCache.getCards({ boardId }); - const lists = await ReactiveCache.getLists({ boardId }); - const swimlanes = await ReactiveCache.getSwimlanes({ boardId }); + const cards = ReactiveCache.getCards({ boardId }); + const lists = ReactiveCache.getLists({ boardId }); + const swimlanes = ReactiveCache.getSwimlanes({ boardId }); // Create maps for efficient lookup const listSwimlaneMap = new Map(); const swimlaneListsMap = new Map(); - + lists.forEach(list => { listSwimlaneMap.set(list._id, list.swimlaneId || ''); if (!swimlaneListsMap.has(list.swimlaneId || '')) { @@ -142,7 +142,7 @@ class FixMissingListsMigration { // Check if we already have a list with the same title in this swimlane let targetList = existingLists.find(list => list.title === originalList.title); - + if (!targetList) { // Create a new list for this swimlane const newListData = { @@ -168,7 +168,7 @@ class FixMissingListsMigration { const newListId = Lists.insert(newListData); targetList = { _id: newListId, ...newListData }; createdLists++; - + if (process.env.DEBUG === 'true') { console.log(`Created new list "${originalList.title}" for swimlane ${swimlaneId}`); } @@ -198,7 +198,7 @@ class FixMissingListsMigration { if (process.env.DEBUG === 'true') { console.log(`Fix missing lists migration completed for board ${boardId}: created ${createdLists} lists, updated ${updatedCards} cards`); } - + return { success: true, createdLists, @@ -214,21 +214,21 @@ class FixMissingListsMigration { /** * Get migration status for a board */ - async getMigrationStatus(boardId) { + getMigrationStatus(boardId) { try { - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (!board) { return { status: 'board_not_found' }; } if (board.fixMissingListsCompleted) { - return { + return { status: 'completed', completedAt: board.fixMissingListsCompletedAt }; } - const needsMigration = await this.needsMigration(boardId); + const needsMigration = this.needsMigration(boardId); return { status: needsMigration ? 'needed' : 'not_needed' }; @@ -245,33 +245,33 @@ export const fixMissingListsMigration = new FixMissingListsMigration(); // Meteor methods Meteor.methods({ - async 'fixMissingListsMigration.check'(boardId) { + 'fixMissingListsMigration.check'(boardId) { check(boardId, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - - return await fixMissingListsMigration.getMigrationStatus(boardId); + + return fixMissingListsMigration.getMigrationStatus(boardId); }, - async 'fixMissingListsMigration.execute'(boardId) { + 'fixMissingListsMigration.execute'(boardId) { check(boardId, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - - return await fixMissingListsMigration.executeMigration(boardId); + + return fixMissingListsMigration.executeMigration(boardId); }, - async 'fixMissingListsMigration.needsMigration'(boardId) { + 'fixMissingListsMigration.needsMigration'(boardId) { check(boardId, String); - + if (!this.userId) { throw new Meteor.Error('not-authorized'); } - - return await fixMissingListsMigration.needsMigration(boardId); + + return fixMissingListsMigration.needsMigration(boardId); } }); diff --git a/server/migrations/migrateAttachments.js b/server/migrations/migrateAttachments.js index 5ff8dcce5..55ffdb0c7 100644 --- a/server/migrations/migrateAttachments.js +++ b/server/migrations/migrateAttachments.js @@ -14,7 +14,7 @@ if (Meteor.isServer) { * @param {string} attachmentId - The old attachment ID * @returns {Object} - Migration result */ - async migrateAttachment(attachmentId) { + migrateAttachment(attachmentId) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } @@ -27,7 +27,7 @@ if (Meteor.isServer) { } // Check if already migrated - const existingAttachment = await ReactiveCache.getAttachment(attachmentId); + const existingAttachment = ReactiveCache.getAttachment(attachmentId); if (existingAttachment) { return { success: true, message: 'Already migrated', attachmentId }; } @@ -72,7 +72,7 @@ if (Meteor.isServer) { * @param {string} cardId - The card ID * @returns {Object} - Migration results */ - async migrateCardAttachments(cardId) { + migrateCardAttachments(cardId) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } @@ -85,7 +85,7 @@ if (Meteor.isServer) { try { // Get all old attachments for this card - const oldAttachments = await ReactiveCache.getAttachments({ 'meta.cardId': cardId }); + const oldAttachments = ReactiveCache.getAttachments({ 'meta.cardId': cardId }); for (const attachment of oldAttachments) { const result = Meteor.call('migrateAttachment', attachment._id); @@ -113,14 +113,14 @@ if (Meteor.isServer) { * @param {string} cardId - The card ID (optional) * @returns {Object} - Migration status */ - async getAttachmentMigrationStatus(cardId) { + getAttachmentMigrationStatus(cardId) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } try { const selector = cardId ? { 'meta.cardId': cardId } : {}; - const allAttachments = await ReactiveCache.getAttachments(selector); + const allAttachments = ReactiveCache.getAttachments(selector); const status = { total: allAttachments.length, diff --git a/server/migrations/restoreAllArchived.js b/server/migrations/restoreAllArchived.js deleted file mode 100644 index a3b703618..000000000 --- a/server/migrations/restoreAllArchived.js +++ /dev/null @@ -1,266 +0,0 @@ -/** - * Restore All Archived Migration - * - * Restores all archived swimlanes, lists, and cards. - * If any restored items are missing swimlaneId, listId, or cardId, - * creates/assigns proper IDs to make them visible. - */ - -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; -import { ReactiveCache } from '/imports/reactiveCache'; -import { TAPi18n } from '/imports/i18n'; -import Boards from '/models/boards'; -import Lists from '/models/lists'; -import Cards from '/models/cards'; -import Swimlanes from '/models/swimlanes'; - -class RestoreAllArchivedMigration { - constructor() { - this.name = 'restoreAllArchived'; - this.version = 1; - } - - /** - * Check if migration is needed for a board - */ - async needsMigration(boardId) { - try { - const archivedSwimlanes = await ReactiveCache.getSwimlanes({ boardId, archived: true }); - const archivedLists = await ReactiveCache.getLists({ boardId, archived: true }); - const archivedCards = await ReactiveCache.getCards({ boardId, archived: true }); - - return archivedSwimlanes.length > 0 || archivedLists.length > 0 || archivedCards.length > 0; - } catch (error) { - console.error('Error checking if restoreAllArchived migration is needed:', error); - return false; - } - } - - /** - * Execute the migration - */ - async executeMigration(boardId) { - try { - const results = { - swimlanesRestored: 0, - listsRestored: 0, - cardsRestored: 0, - itemsFixed: 0, - errors: [] - }; - - const board = await ReactiveCache.getBoard(boardId); - if (!board) { - throw new Error('Board not found'); - } - - // Get archived items - const archivedSwimlanes = await ReactiveCache.getSwimlanes({ boardId, archived: true }); - const archivedLists = await ReactiveCache.getLists({ boardId, archived: true }); - const archivedCards = await ReactiveCache.getCards({ boardId, archived: true }); - - // Get active items for reference - const activeSwimlanes = await ReactiveCache.getSwimlanes({ boardId, archived: false }); - const activeLists = await ReactiveCache.getLists({ boardId, archived: false }); - - // Restore all archived swimlanes - for (const swimlane of archivedSwimlanes) { - Swimlanes.update(swimlane._id, { - $set: { - archived: false, - updatedAt: new Date() - } - }); - results.swimlanesRestored++; - - if (process.env.DEBUG === 'true') { - console.log(`Restored swimlane: ${swimlane.title}`); - } - } - - // Restore all archived lists and fix missing swimlaneId - for (const list of archivedLists) { - const updateFields = { - archived: false, - updatedAt: new Date() - }; - - // Fix missing swimlaneId - if (!list.swimlaneId) { - // Try to find a suitable swimlane or use default - let targetSwimlane = activeSwimlanes.find(s => !s.archived); - - if (!targetSwimlane) { - // No active swimlane found, create default - const swimlaneId = Swimlanes.insert({ - title: TAPi18n.__('default'), - boardId: boardId, - sort: 0, - createdAt: new Date(), - updatedAt: new Date(), - archived: false - }); - targetSwimlane = await ReactiveCache.getSwimlane(swimlaneId); - } - - updateFields.swimlaneId = targetSwimlane._id; - results.itemsFixed++; - - if (process.env.DEBUG === 'true') { - console.log(`Fixed missing swimlaneId for list: ${list.title}`); - } - } - - Lists.update(list._id, { - $set: updateFields - }); - results.listsRestored++; - - if (process.env.DEBUG === 'true') { - console.log(`Restored list: ${list.title}`); - } - } - - // Refresh lists after restoration - const allLists = await ReactiveCache.getLists({ boardId, archived: false }); - const allSwimlanes = await ReactiveCache.getSwimlanes({ boardId, archived: false }); - - // Restore all archived cards and fix missing IDs - for (const card of archivedCards) { - const updateFields = { - archived: false, - updatedAt: new Date() - }; - - let needsFix = false; - - // Fix missing listId - if (!card.listId) { - // Find or create a default list - let targetList = allLists.find(l => !l.archived); - - if (!targetList) { - // No active list found, create one - const defaultSwimlane = allSwimlanes.find(s => !s.archived) || allSwimlanes[0]; - - const listId = Lists.insert({ - title: TAPi18n.__('default'), - boardId: boardId, - swimlaneId: defaultSwimlane._id, - sort: 0, - createdAt: new Date(), - updatedAt: new Date(), - archived: false - }); - targetList = await ReactiveCache.getList(listId); - } - - updateFields.listId = targetList._id; - needsFix = true; - } - - // Fix missing swimlaneId - if (!card.swimlaneId) { - // Try to get swimlaneId from the card's list - if (card.listId || updateFields.listId) { - const cardList = allLists.find(l => l._id === (updateFields.listId || card.listId)); - if (cardList && cardList.swimlaneId) { - updateFields.swimlaneId = cardList.swimlaneId; - } else { - // Fall back to first available swimlane - const defaultSwimlane = allSwimlanes.find(s => !s.archived) || allSwimlanes[0]; - updateFields.swimlaneId = defaultSwimlane._id; - } - } else { - // Fall back to first available swimlane - const defaultSwimlane = allSwimlanes.find(s => !s.archived) || allSwimlanes[0]; - updateFields.swimlaneId = defaultSwimlane._id; - } - needsFix = true; - } - - if (needsFix) { - results.itemsFixed++; - - if (process.env.DEBUG === 'true') { - console.log(`Fixed missing IDs for card: ${card.title}`); - } - } - - Cards.update(card._id, { - $set: updateFields - }); - results.cardsRestored++; - - if (process.env.DEBUG === 'true') { - console.log(`Restored card: ${card.title}`); - } - } - - return { - success: true, - changes: [ - `Restored ${results.swimlanesRestored} archived swimlanes`, - `Restored ${results.listsRestored} archived lists`, - `Restored ${results.cardsRestored} archived cards`, - `Fixed ${results.itemsFixed} items with missing IDs` - ], - results - }; - } catch (error) { - console.error('Error executing restoreAllArchived migration:', error); - return { - success: false, - error: error.message - }; - } - } -} - -const restoreAllArchivedMigration = new RestoreAllArchivedMigration(); - -// Register Meteor methods -Meteor.methods({ - async 'restoreAllArchived.needsMigration'(boardId) { - check(boardId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in'); - } - - return await restoreAllArchivedMigration.needsMigration(boardId); - }, - - async 'restoreAllArchived.execute'(boardId) { - check(boardId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in'); - } - - // Check if user is board admin - const board = await ReactiveCache.getBoard(boardId); - if (!board) { - throw new Meteor.Error('board-not-found', 'Board not found'); - } - - const user = await ReactiveCache.getUser(this.userId); - if (!user) { - throw new Meteor.Error('user-not-found', 'User not found'); - } - - // Only board admins can run migrations - const isBoardAdmin = board.members && board.members.some( - member => member.userId === this.userId && member.isAdmin - ); - - if (!isBoardAdmin && !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Only board administrators can run migrations'); - } - - return await restoreAllArchivedMigration.executeMigration(boardId); - } -}); - -export default restoreAllArchivedMigration; diff --git a/server/migrations/restoreLostCards.js b/server/migrations/restoreLostCards.js deleted file mode 100644 index d6cc835a1..000000000 --- a/server/migrations/restoreLostCards.js +++ /dev/null @@ -1,259 +0,0 @@ -/** - * Restore Lost Cards Migration - * - * Finds and restores cards and lists that have missing swimlaneId, listId, or are orphaned. - * Creates a "Lost Cards" swimlane and restores visibility of lost items. - * Only processes non-archived items. - */ - -import { Meteor } from 'meteor/meteor'; -import { check } from 'meteor/check'; -import { ReactiveCache } from '/imports/reactiveCache'; -import { TAPi18n } from '/imports/i18n'; -import Boards from '/models/boards'; -import Lists from '/models/lists'; -import Cards from '/models/cards'; -import Swimlanes from '/models/swimlanes'; - -class RestoreLostCardsMigration { - constructor() { - this.name = 'restoreLostCards'; - this.version = 1; - } - - /** - * Check if migration is needed for a board - */ - async needsMigration(boardId) { - try { - const cards = await ReactiveCache.getCards({ boardId, archived: false }); - const lists = await ReactiveCache.getLists({ boardId, archived: false }); - - // Check for cards missing swimlaneId or listId - const lostCards = cards.filter(card => !card.swimlaneId || !card.listId); - if (lostCards.length > 0) { - return true; - } - - // Check for lists missing swimlaneId - const lostLists = lists.filter(list => !list.swimlaneId); - if (lostLists.length > 0) { - return true; - } - - // Check for orphaned cards (cards whose list doesn't exist) - for (const card of cards) { - if (card.listId) { - const listExists = lists.some(list => list._id === card.listId); - if (!listExists) { - return true; - } - } - } - - return false; - } catch (error) { - console.error('Error checking if restoreLostCards migration is needed:', error); - return false; - } - } - - /** - * Execute the migration - */ - async executeMigration(boardId) { - try { - const results = { - lostCardsSwimlaneCreated: false, - cardsRestored: 0, - listsRestored: 0, - errors: [] - }; - - const board = await ReactiveCache.getBoard(boardId); - if (!board) { - throw new Error('Board not found'); - } - - // Get all non-archived items - const cards = await ReactiveCache.getCards({ boardId, archived: false }); - const lists = await ReactiveCache.getLists({ boardId, archived: false }); - const swimlanes = await ReactiveCache.getSwimlanes({ boardId, archived: false }); - - // Detect items to restore BEFORE creating anything - const lostLists = lists.filter(list => !list.swimlaneId); - const lostCards = cards.filter(card => !card.swimlaneId || !card.listId); - const orphanedCards = cards.filter(card => card.listId && !lists.some(list => list._id === card.listId)); - - const hasCardsWork = lostCards.length > 0 || orphanedCards.length > 0; - const hasListsWork = lostLists.length > 0; - const hasAnyWork = hasCardsWork || hasListsWork; - - if (!hasAnyWork) { - // Nothing to restore; do not create swimlane or list - return { - success: true, - changes: [ - 'No lost swimlanes, lists, or cards to restore' - ], - results: { - lostCardsSwimlaneCreated: false, - cardsRestored: 0, - listsRestored: 0 - } - }; - } - - // Find or create "Lost Cards" swimlane (only if there is actual work) - let lostCardsSwimlane = swimlanes.find(s => s.title === TAPi18n.__('lost-cards')); - if (!lostCardsSwimlane) { - const swimlaneId = Swimlanes.insert({ - title: TAPi18n.__('lost-cards'), - boardId: boardId, - sort: 999999, // Put at the end - color: 'red', - createdAt: new Date(), - updatedAt: new Date(), - archived: false - }); - lostCardsSwimlane = await ReactiveCache.getSwimlane(swimlaneId); - results.lostCardsSwimlaneCreated = true; - if (process.env.DEBUG === 'true') { - console.log(`Created "Lost Cards" swimlane for board ${boardId}`); - } - } - - // Restore lost lists (lists without swimlaneId) - if (hasListsWork) { - for (const list of lostLists) { - Lists.update(list._id, { - $set: { - swimlaneId: lostCardsSwimlane._id, - updatedAt: new Date() - } - }); - results.listsRestored++; - if (process.env.DEBUG === 'true') { - console.log(`Restored lost list: ${list.title}`); - } - } - } - - // Create default list only if we need to move cards - let defaultList = null; - if (hasCardsWork) { - defaultList = lists.find(l => - l.swimlaneId === lostCardsSwimlane._id && - l.title === TAPi18n.__('lost-cards-list') - ); - if (!defaultList) { - const listId = Lists.insert({ - title: TAPi18n.__('lost-cards-list'), - boardId: boardId, - swimlaneId: lostCardsSwimlane._id, - sort: 0, - createdAt: new Date(), - updatedAt: new Date(), - archived: false - }); - defaultList = await ReactiveCache.getList(listId); - if (process.env.DEBUG === 'true') { - console.log(`Created default list in Lost Cards swimlane`); - } - } - } - - // Restore cards missing swimlaneId or listId - if (hasCardsWork) { - for (const card of lostCards) { - const updateFields = { updatedAt: new Date() }; - if (!card.swimlaneId) updateFields.swimlaneId = lostCardsSwimlane._id; - if (!card.listId) updateFields.listId = defaultList._id; - Cards.update(card._id, { $set: updateFields }); - results.cardsRestored++; - if (process.env.DEBUG === 'true') { - console.log(`Restored lost card: ${card.title}`); - } - } - - // Restore orphaned cards (cards whose list doesn't exist) - for (const card of orphanedCards) { - Cards.update(card._id, { - $set: { - listId: defaultList._id, - swimlaneId: lostCardsSwimlane._id, - updatedAt: new Date() - } - }); - results.cardsRestored++; - if (process.env.DEBUG === 'true') { - console.log(`Restored orphaned card: ${card.title}`); - } - } - } - - return { - success: true, - changes: [ - results.lostCardsSwimlaneCreated ? 'Created "Lost Cards" swimlane' : 'Using existing "Lost Cards" swimlane', - `Restored ${results.listsRestored} lost lists`, - `Restored ${results.cardsRestored} lost cards` - ], - results - }; - } catch (error) { - console.error('Error executing restoreLostCards migration:', error); - return { - success: false, - error: error.message - }; - } - } -} - -const restoreLostCardsMigration = new RestoreLostCardsMigration(); - -// Register Meteor methods -Meteor.methods({ - async 'restoreLostCards.needsMigration'(boardId) { - check(boardId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in'); - } - - return await restoreLostCardsMigration.needsMigration(boardId); - }, - - async 'restoreLostCards.execute'(boardId) { - check(boardId, String); - - if (!this.userId) { - throw new Meteor.Error('not-authorized', 'You must be logged in'); - } - - // Check if user is board admin - const board = await ReactiveCache.getBoard(boardId); - if (!board) { - throw new Meteor.Error('board-not-found', 'Board not found'); - } - - const user = await ReactiveCache.getUser(this.userId); - if (!user) { - throw new Meteor.Error('user-not-found', 'User not found'); - } - - // Only board admins can run migrations - const isBoardAdmin = board.members && board.members.some( - member => member.userId === this.userId && member.isAdmin - ); - - if (!isBoardAdmin && !user.isAdmin) { - throw new Meteor.Error('not-authorized', 'Only board administrators can run migrations'); - } - - return await restoreLostCardsMigration.executeMigration(boardId); - } -}); - -export default restoreLostCardsMigration; diff --git a/server/mongodb-driver-startup.js b/server/mongodb-driver-startup.js index 9eaae05e4..8ced105ee 100644 --- a/server/mongodb-driver-startup.js +++ b/server/mongodb-driver-startup.js @@ -5,7 +5,7 @@ import { meteorMongoIntegration } from '/models/lib/meteorMongoIntegration'; /** * MongoDB Driver Startup - * + * * This module initializes the MongoDB driver system on server startup, * providing automatic version detection and driver selection for * MongoDB versions 3.0 through 8.0. @@ -14,7 +14,7 @@ import { meteorMongoIntegration } from '/models/lib/meteorMongoIntegration'; // Initialize MongoDB driver system on server startup Meteor.startup(async function() { // MongoDB Driver System Startup (status available in Admin Panel) - + try { // Check if MONGO_URL is available const mongoUrl = process.env.MONGO_URL; @@ -31,7 +31,7 @@ Meteor.startup(async function() { // Test the connection const testResult = await meteorMongoIntegration.testConnection(); - + if (testResult.success) { // MongoDB connection test successful // Driver and version information available in Admin Panel @@ -51,7 +51,7 @@ Meteor.startup(async function() { } catch (error) { console.error('Error during MongoDB driver system startup:', error.message); console.error('Stack trace:', error.stack); - + // Don't fail the entire startup, just log the error console.log('Continuing with default MongoDB connection...'); } @@ -65,7 +65,7 @@ if (Meteor.isServer) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } - + return { connectionStats: mongodbConnectionManager.getConnectionStats(), driverStats: mongodbDriverManager.getConnectionStats(), @@ -77,7 +77,7 @@ if (Meteor.isServer) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } - + return await meteorMongoIntegration.testConnection(); }, @@ -85,7 +85,7 @@ if (Meteor.isServer) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } - + meteorMongoIntegration.reset(); return { success: true, message: 'MongoDB driver system reset' }; }, @@ -94,7 +94,7 @@ if (Meteor.isServer) { if (!this.userId) { throw new Meteor.Error('not-authorized', 'Must be logged in'); } - + return { supportedVersions: mongodbDriverManager.getSupportedVersions(), compatibility: mongodbDriverManager.getSupportedVersions().map(version => { @@ -120,11 +120,11 @@ if (Meteor.isServer) { } const self = this; - + // Send initial data const stats = meteorMongoIntegration.getStats(); self.added('mongodbDriverMonitor', 'stats', stats); - + // Update every 30 seconds const interval = setInterval(() => { const updatedStats = meteorMongoIntegration.getStats(); diff --git a/server/notifications/email.js b/server/notifications/email.js index 3b9706402..7b1bf9a42 100644 --- a/server/notifications/email.js +++ b/server/notifications/email.js @@ -33,8 +33,8 @@ Meteor.startup(() => { // Meteor.setTimeout(func, delay) does not accept args :-( // so we pass userId with closure const userId = user._id; - Meteor.setTimeout(async () => { - const user = await ReactiveCache.getUser(userId); + Meteor.setTimeout(() => { + const user = ReactiveCache.getUser(userId); // for each user, in the timed period, only the first call will get the cached content, // other calls will get nothing diff --git a/server/notifications/notifications.js b/server/notifications/notifications.js index 0be96f25f..783ad9f3f 100644 --- a/server/notifications/notifications.js +++ b/server/notifications/notifications.js @@ -19,19 +19,16 @@ Notifications = { delete notifyServices[serviceName]; }, - getUsers: async watchers => { + getUsers: watchers => { const users = []; - for (const userId of watchers) { - const user = await ReactiveCache.getUser(userId); - if (user && user._id) users.push(user); - } + watchers.forEach(userId => { + const user = ReactiveCache.getUser(userId); + if (user) users.push(user); + }); return users; }, notify: (user, title, description, params) => { - // Skip if user is invalid - if (!user || !user._id) return; - for (const k in notifyServices) { const notifyImpl = notifyServices[k]; if (notifyImpl && typeof notifyImpl === 'function') diff --git a/server/notifications/outgoing.js b/server/notifications/outgoing.js index afabf549c..ff5123946 100644 --- a/server/notifications/outgoing.js +++ b/server/notifications/outgoing.js @@ -12,43 +12,6 @@ if (Meteor.isServer) { }); }); - // SSRF Protection: Validate webhook URLs before posting - const isValidWebhookUrl = (urlString) => { - try { - const u = new URL(urlString); - - // Only allow http and https protocols - if (!['http:', 'https:'].includes(u.protocol)) { - return false; - } - - // Block private/loopback IP ranges and hostnames - const hostname = u.hostname.toLowerCase(); - const blockedPatterns = [ - /^127\./, // 127.x.x.x (loopback) - /^10\./, // 10.x.x.x (private) - /^172\.(1[6-9]|2\d|3[01])\./, // 172.16-31.x.x (private) - /^192\.168\./, // 192.168.x.x (private) - /^0\./, // 0.x.x.x (current network) - /^::1$/, // IPv6 loopback - /^fe80:/, // IPv6 link-local - /^fc00:/, // IPv6 unique local - /^fd00:/, // IPv6 unique local - /^localhost$/i, - /\.local$/i, - /^169\.254\./, // link-local IP (AWS metadata) - ]; - - if (blockedPatterns.some(pattern => pattern.test(hostname))) { - return false; - } - - return true; - } catch { - return false; - } - }; - const Lock = { _lock: {}, _timer: {}, @@ -107,46 +70,46 @@ if (Meteor.isServer) { 'label', 'attachmentId', ]; - const responseFunc = async (data, integration) => { + const responseFunc = data => { const paramCommentId = data.commentId; const paramCardId = data.cardId; const paramBoardId = data.boardId; const newComment = data.comment; - - // Authorization: Verify the request is from a bidirectional webhook - if (!integration || integration.type !== Integrations.Const.TWOWAY) { - return; // Only bidirectional webhooks can update comments - } - - // Authorization: Prevent cross-board comment injection - if (paramBoardId !== integration.boardId) { - return; // Webhook can only modify comments in its own board - } - - if (paramCardId && paramBoardId && newComment && paramCommentId) { - // only process data with the commentId, cardId, boardId and comment text - const comment = await ReactiveCache.getCardComment({ + if (paramCardId && paramBoardId && newComment) { + // only process data with the cardid, boardid and comment text, TODO can expand other functions here to react on returned data + const comment = ReactiveCache.getCardComment({ _id: paramCommentId, cardId: paramCardId, boardId: paramBoardId, }); - const board = await ReactiveCache.getBoard(paramBoardId); - const card = await ReactiveCache.getCard(paramCardId); - - if (board && card && comment) { - // Only update existing comments - do not create new comments from webhook responses - Lock.set(comment._id, newComment); - CardComments.direct.update(comment._id, { - $set: { + const board = ReactiveCache.getBoard(paramBoardId); + const card = ReactiveCache.getCard(paramCardId); + if (board && card) { + if (comment) { + Lock.set(comment._id, newComment); + CardComments.direct.update(comment._id, { + $set: { + text: newComment, + }, + }); + } + } else { + const userId = data.userId; + if (userId) { + const inserted = CardComments.direct.insert({ text: newComment, - }, - }); + userId, + cardId, + boardId, + }); + Lock.set(inserted._id, newComment); + } } } }; Meteor.methods({ - async outgoingWebhooks(integration, description, params) { - if (await ReactiveCache.getCurrentUser()) { + outgoingWebhooks(integration, description, params) { + if (ReactiveCache.getCurrentUser()) { check(integration, Object); check(description, String); check(params, Object); @@ -174,7 +137,7 @@ if (Meteor.isServer) { }); const userId = params.userId ? params.userId : integrations[0].userId; - const user = await ReactiveCache.getUser(userId); + const user = ReactiveCache.getUser(userId); const descriptionText = TAPi18n.__( description, quoteParams, @@ -208,15 +171,10 @@ if (Meteor.isServer) { data: is2way ? { description, ...clonedParams } : value, }; - if (!(await ReactiveCache.getIntegration({ url: integration.url }))) return; + if (!ReactiveCache.getIntegration({ url: integration.url })) return; const url = integration.url; - // SSRF Protection: Validate webhook URL before posting - if (!isValidWebhookUrl(url)) { - throw new Meteor.Error('invalid-webhook-url', 'Webhook URL is invalid or points to a private/internal address'); - } - if (is2way) { const cid = params.commentId; const comment = params.comment; @@ -240,7 +198,7 @@ if (Meteor.isServer) { const data = response.data; // only an JSON encoded response will be actioned if (data) { try { - await responseFunc(data, integration); + responseFunc(data); } catch (e) { throw new Meteor.Error('error-process-data'); } diff --git a/server/notifications/profile.js b/server/notifications/profile.js index 71e5fcd3f..608931cf9 100644 --- a/server/notifications/profile.js +++ b/server/notifications/profile.js @@ -1,15 +1,5 @@ Meteor.startup(() => { Notifications.subscribe('profile', (user, title, description, params) => { - try { - // Validate user object before processing - if (!user || !user._id) { - console.error('Invalid user object in notification:', { user, title, params }); - return; - } - const modifier = user.addNotification(params.activityId); - Users.direct.update(user._id, modifier); - } catch (error) { - console.error('Error adding notification:', error); - } + user.addNotification(params.activityId); }); }); diff --git a/server/notifications/watch.js b/server/notifications/watch.js index cb9cf5029..b6e1a6092 100644 --- a/server/notifications/watch.js +++ b/server/notifications/watch.js @@ -1,7 +1,7 @@ import { ReactiveCache } from '/imports/reactiveCache'; Meteor.methods({ - async watch(watchableType, id, level) { + watch(watchableType, id, level) { check(watchableType, String); check(id, String); check(level, Match.OneOf(String, null)); @@ -11,17 +11,17 @@ Meteor.methods({ let watchableObj = null; let board = null; if (watchableType === 'board') { - watchableObj = await ReactiveCache.getBoard(id); + watchableObj = ReactiveCache.getBoard(id); if (!watchableObj) throw new Meteor.Error('error-board-doesNotExist'); board = watchableObj; } else if (watchableType === 'list') { - watchableObj = await ReactiveCache.getList(id); + watchableObj = ReactiveCache.getList(id); if (!watchableObj) throw new Meteor.Error('error-list-doesNotExist'); - board = await watchableObj.board(); + board = watchableObj.board(); } else if (watchableType === 'card') { - watchableObj = await ReactiveCache.getCard(id); + watchableObj = ReactiveCache.getCard(id); if (!watchableObj) throw new Meteor.Error('error-card-doesNotExist'); - board = await watchableObj.board(); + board = watchableObj.board(); } else { throw new Meteor.Error('error-json-schema'); } @@ -29,7 +29,7 @@ Meteor.methods({ if (board.permission === 'private' && !board.hasMember(userId)) throw new Meteor.Error('error-board-notAMember'); - await watchableObj.setWatcher(userId, level); + watchableObj.setWatcher(userId, level); return true; }, }); diff --git a/server/publications/activities.js b/server/publications/activities.js index ffb545fa0..e55d627c0 100644 --- a/server/publications/activities.js +++ b/server/publications/activities.js @@ -5,7 +5,7 @@ import { ReactiveCache } from '/imports/reactiveCache'; // 2. The card activity tab // We use this publication to paginate for these two publications. -Meteor.publish('activities', async function(kind, id, limit, showActivities) { +Meteor.publish('activities', function(kind, id, limit, showActivities) { check( kind, Match.Where(x => { @@ -21,45 +21,21 @@ Meteor.publish('activities', async function(kind, id, limit, showActivities) { return this.ready(); } - if (!this.userId) { - return this.ready(); - } - + // Get linkedBoard let linkedElmtId = [id]; - let board; - - if (kind === 'board') { - board = await ReactiveCache.getBoard(id); - if (!board || !board.isVisibleBy(this.userId)) { - return this.ready(); - } - - // Get linked boards, but only those visible to the user - const linkedCards = await ReactiveCache.getCards({ + if (kind == 'board') { + ReactiveCache.getCards({ "type": "cardType-linkedBoard", - "boardId": id - }); - for (const card of linkedCards) { - const linkedBoard = await ReactiveCache.getBoard(card.linkedId); - if (linkedBoard && linkedBoard.isVisibleBy(this.userId)) { + "boardId": id} + ).forEach(card => { linkedElmtId.push(card.linkedId); - } - } - } else if (kind === 'card') { - const card = await ReactiveCache.getCard(id); - if (!card) { - return this.ready(); - } - board = await ReactiveCache.getBoard(card.boardId); - if (!board || !board.isVisibleBy(this.userId)) { - return this.ready(); - } + }); } const selector = showActivities ? { [`${kind}Id`]: { $in: linkedElmtId } } : { $and: [{ activityType: 'addComment' }, { [`${kind}Id`]: { $in: linkedElmtId } }] }; - const ret = await ReactiveCache.getActivities(selector, + const ret = ReactiveCache.getActivities(selector, { limit, sort: { createdAt: -1 }, diff --git a/server/publications/attachmentMigrationStatus.js b/server/publications/attachmentMigrationStatus.js deleted file mode 100644 index 11b50f593..000000000 --- a/server/publications/attachmentMigrationStatus.js +++ /dev/null @@ -1,43 +0,0 @@ -import { AttachmentMigrationStatus } from '../attachmentMigrationStatus'; - -// Publish attachment migration status for boards user has access to -Meteor.publish('attachmentMigrationStatus', function(boardId) { - if (!this.userId) { - return this.ready(); - } - - check(boardId, String); - - const board = Boards.findOne(boardId); - if (!board || !board.isVisibleBy({ _id: this.userId })) { - return this.ready(); - } - - // Publish migration status for this board - return AttachmentMigrationStatus.find({ boardId }); -}); - -// Publish all attachment migration statuses for user's boards -Meteor.publish('attachmentMigrationStatuses', function() { - if (!this.userId) { - return this.ready(); - } - - const user = Users.findOne(this.userId); - if (!user) { - return this.ready(); - } - - // Get all boards user has access to - const boards = Boards.find({ - $or: [ - { 'members.userId': this.userId }, - { isPublic: true } - ] - }, { fields: { _id: 1 } }).fetch(); - - const boardIds = boards.map(b => b._id); - - // Publish migration status for all user's boards - return AttachmentMigrationStatus.find({ boardId: { $in: boardIds } }); -}); diff --git a/server/publications/attachments.js b/server/publications/attachments.js index a25c82487..ae421b8c8 100644 --- a/server/publications/attachments.js +++ b/server/publications/attachments.js @@ -1,36 +1,9 @@ import Attachments from '/models/attachments'; import { ObjectID } from 'bson'; -Meteor.publish('attachmentsList', async function(limit) { - const userId = this.userId; - - // Get boards the user has access to - const userBoards = (await ReactiveCache.getBoards({ - $or: [ - { permission: 'public' }, - { members: { $elemMatch: { userId, isActive: true } } } - ] - })).map(board => board._id); - - if (userBoards.length === 0) { - // User has no access to any boards, return empty cursor - return this.ready(); - } - - // Get cards from those boards - const userCards = (await ReactiveCache.getCards({ - boardId: { $in: userBoards }, - archived: false - })).map(card => card._id); - - if (userCards.length === 0) { - // No cards found, return empty cursor - return this.ready(); - } - - // Only return attachments for cards the user has access to - const ret = (await ReactiveCache.getAttachments( - { 'meta.cardId': { $in: userCards } }, +Meteor.publish('attachmentsList', function(limit) { + const ret = ReactiveCache.getAttachments( + {}, { fields: { _id: 1, @@ -47,6 +20,6 @@ Meteor.publish('attachmentsList', async function(limit) { limit: limit, }, true, - )).cursor; + ).cursor; return ret; }); diff --git a/server/publications/avatars.js b/server/publications/avatars.js index d7ca862f4..47f9f0bdd 100644 --- a/server/publications/avatars.js +++ b/server/publications/avatars.js @@ -1,5 +1,5 @@ import Avatars from '../../models/avatars'; -Meteor.publish('my-avatars', async function() { - const ret = (await ReactiveCache.getAvatars({ userId: this.userId }, {}, true)).cursor; +Meteor.publish('my-avatars', function() { + const ret = ReactiveCache.getAvatars({ userId: this.userId }, {}, true).cursor; return ret; }); diff --git a/server/publications/boards.js b/server/publications/boards.js index b2ac4509a..dee05959f 100644 --- a/server/publications/boards.js +++ b/server/publications/boards.js @@ -3,13 +3,12 @@ // 1. that the user is a member of // 2. the user has starred import { ReactiveCache } from '/imports/reactiveCache'; -import { publishComposite } from 'meteor/reywood:publish-composite'; import Users from "../../models/users"; import Org from "../../models/org"; import Team from "../../models/team"; import Attachments from '../../models/attachments'; -publishComposite('boards', function() { +Meteor.publishRelations('boards', function() { const userId = this.userId; // Ensure that the user is connected. If it is not, we need to return an empty // array to tell the client to remove the previously published docs. @@ -17,72 +16,85 @@ publishComposite('boards', function() { return []; } - return { - async find() { - return await ReactiveCache.getBoards( - { - archived: false, - _id: { $in: await Boards.userBoardIds(userId, false) }, - }, - { - sort: { sort: 1 /* boards default sorting */ }, - }, - true, - ); + // Defensive programming to verify that starredBoards has the expected + // format -- since the field is in the `profile` a user can modify it. + // const { starredBoards = [] } = (ReactiveCache.getUser(userId) || {}).profile || {}; + // check(starredBoards, [String]); + + // let currUser = ReactiveCache.getUser(userId); + // let orgIdsUserBelongs = currUser!== 'undefined' && currUser.teams !== 'undefined' ? currUser.orgIdsUserBelongs() : ''; + // let teamIdsUserBelongs = currUser!== 'undefined' && currUser.teams !== 'undefined' ? currUser.teamIdsUserBelongs() : ''; + // let orgsIds = []; + // let teamsIds = []; + // if(orgIdsUserBelongs && orgIdsUserBelongs != ''){ + // orgsIds = orgIdsUserBelongs.split(','); + // } + // if(teamIdsUserBelongs && teamIdsUserBelongs != ''){ + // teamsIds = teamIdsUserBelongs.split(','); + // } + this.cursor(ReactiveCache.getBoards( + { + archived: false, + _id: { $in: Boards.userBoardIds(userId, false) }, + // $or: [ + // { + // // _id: { $in: starredBoards }, // Commented out, to get a list of all public boards + // permission: 'public', + // }, + // { members: { $elemMatch: { userId, isActive: true } } }, + // {'orgs.orgId': {$in : orgsIds}}, + // {'teams.teamId': {$in : teamsIds}}, + // ], }, - children: [ - { - async find(board) { - // Publish lists with extended fields for proper sync - // Including swimlaneId, modifiedAt, and _updatedAt for list order changes - return await ReactiveCache.getLists( - { boardId: board._id, archived: false }, + { + sort: { sort: 1 /* boards default sorting */ }, + }, + true, + ), + function(boardId, board) { + this.cursor( + ReactiveCache.getLists( + { boardId, archived: false }, + { fields: { - fields: { - _id: 1, - title: 1, - boardId: 1, - swimlaneId: 1, - archived: 1, - sort: 1, - modifiedAt: 1, - _updatedAt: 1, // Hidden field to trigger updates - } - }, - true, - ); - } - }, - { - async find(board) { - return await ReactiveCache.getCards( - { boardId: board._id, archived: false }, - { - fields: { - _id: 1, - boardId: 1, - listId: 1, - archived: 1, - sort: 1 - } - }, - true, - ); - } - } - ] - }; + _id: 1, + title: 1, + boardId: 1, + archived: 1, + sort: 1 + } + }, + true, + ) + ); + this.cursor( + ReactiveCache.getCards( + { boardId, archived: false }, + { fields: { + _id: 1, + boardId: 1, + listId: 1, + archived: 1, + sort: 1 + }}, + true, + ) + ); + } + ); + const ret = this.ready(); + return ret; }); -Meteor.publish('boardsReport', async function() { +Meteor.publish('boardsReport', function() { const userId = this.userId; // Ensure that the user is connected. If it is not, we need to return an empty // array to tell the client to remove the previously published docs. if (!Match.test(userId, String) || !userId) return []; - const boards = await ReactiveCache.getBoards( + const boards = ReactiveCache.getBoards( { - _id: { $in: await Boards.userBoardIds(userId, null) }, + _id: { $in: Boards.userBoardIds(userId, null) }, }, { fields: { @@ -129,20 +141,20 @@ Meteor.publish('boardsReport', async function() { const ret = [ boards, - await ReactiveCache.getUsers({ _id: { $in: userIds } }, { fields: Users.safeFields }, true), - await ReactiveCache.getTeams({ _id: { $in: teamIds } }, {}, true), - await ReactiveCache.getOrgs({ _id: { $in: orgIds } }, {}, true), + ReactiveCache.getUsers({ _id: { $in: userIds } }, { fields: Users.safeFields }, true), + ReactiveCache.getTeams({ _id: { $in: teamIds } }, {}, true), + ReactiveCache.getOrgs({ _id: { $in: orgIds } }, {}, true), ] return ret; }); -Meteor.publish('archivedBoards', async function() { +Meteor.publish('archivedBoards', function() { const userId = this.userId; if (!Match.test(userId, String)) return []; - const ret = await ReactiveCache.getBoards( + const ret = ReactiveCache.getBoards( { - _id: { $in: await Boards.userBoardIds(userId, true)}, + _id: { $in: Boards.userBoardIds(userId, true)}, archived: true, members: { $elemMatch: { @@ -168,394 +180,185 @@ Meteor.publish('archivedBoards', async function() { return ret; }); -// OPTIMIZED BOARD PUBLICATION -// -// Performance improvements implemented to reduce N+1 query problem: -// - Batches card-related queries (comments, attachments, checklists) instead of querying per-card -// - Uses field projections to minimize data transfer -// - Removed automatic loading of entire linked boards (cardType-linkedBoard) -// - Only loads visible data: cards, comments, attachments, checklists for current board -// -// Estimated improvement: -// - Before: ~800-1000 queries for board with 100 cards -// - After: ~15-20 batched queries for same board (40-50x reduction) -// // If isArchived = false, this will only return board elements which are not archived. // If isArchived = true, this will only return board elements which are archived. -publishComposite('board', async function(boardId, isArchived) { +Meteor.publishRelations('board', function(boardId, isArchived) { + this.unblock(); check(boardId, String); check(isArchived, Boolean); - const thisUserId = this.userId; const $or = [{ permission: 'public' }]; - - let currUser = (!Match.test(thisUserId, String) || !thisUserId) ? 'undefined' : await ReactiveCache.getUser(thisUserId); - let orgIdsUserBelongs = currUser !== 'undefined' && currUser.teams !== 'undefined' ? currUser.orgIdsUserBelongs() : ''; - let teamIdsUserBelongs = currUser !== 'undefined' && currUser.teams !== 'undefined' ? currUser.teamIdsUserBelongs() : ''; + let currUser = (!Match.test(thisUserId, String) || !thisUserId) ? 'undefined' : ReactiveCache.getUser(thisUserId); + let orgIdsUserBelongs = currUser!== 'undefined' && currUser.teams !== 'undefined' ? currUser.orgIdsUserBelongs() : ''; + let teamIdsUserBelongs = currUser!== 'undefined' && currUser.teams !== 'undefined' ? currUser.teamIdsUserBelongs() : ''; let orgsIds = []; let teamsIds = []; - - if (orgIdsUserBelongs && orgIdsUserBelongs != '') { + if(orgIdsUserBelongs && orgIdsUserBelongs != ''){ orgsIds = orgIdsUserBelongs.split(','); } - if (teamIdsUserBelongs && teamIdsUserBelongs != '') { + if(teamIdsUserBelongs && teamIdsUserBelongs != ''){ teamsIds = teamIdsUserBelongs.split(','); } if (thisUserId) { - $or.push({ members: { $elemMatch: { userId: thisUserId, isActive: true } } }); - $or.push({ 'orgs.orgId': { $in: orgsIds } }); - $or.push({ 'teams.teamId': { $in: teamsIds } }); + $or.push({members: { $elemMatch: { userId: thisUserId, isActive: true } }}); + $or.push({'orgs.orgId': {$in : orgsIds}}); + $or.push({'teams.teamId': {$in : teamsIds}}); } - return { - async find() { - return await ReactiveCache.getBoards( - { - _id: boardId, - archived: false, - // If the board is not public the user has to be a member of it to see it. - $or, - }, - { limit: 1, sort: { sort: 1 /* boards default sorting */ } }, - true, + this.cursor( + ReactiveCache.getBoards( + { + _id: boardId, + archived: false, + // If the board is not public the user has to be a member of it to see + // it. + $or, + // Sort required to ensure oplog usage + }, + { limit: 1, sort: { sort: 1 /* boards default sorting */ } }, + true, + ), + function(boardId, board) { + this.cursor(ReactiveCache.getLists({ boardId, archived: isArchived }, {}, true)); + this.cursor(ReactiveCache.getSwimlanes({ boardId, archived: isArchived }, {}, true)); + this.cursor(ReactiveCache.getIntegrations({ boardId }, {}, true)); + this.cursor(ReactiveCache.getCardCommentReactions({ boardId }, {}, true)); + this.cursor( + ReactiveCache.getCustomFields( + { boardIds: { $in: [boardId] } }, + { sort: { name: 1 } }, + true, + ), ); - }, - children: [ - // Lists - { - async find(board) { - return await ReactiveCache.getLists({ boardId: board._id, archived: isArchived }, {}, true); - } - }, - // Swimlanes - { - async find(board) { - return await ReactiveCache.getSwimlanes({ boardId: board._id, archived: isArchived }, {}, true); - } - }, - // Integrations - { - async find(board) { - return await ReactiveCache.getIntegrations( - { boardId: board._id }, - { fields: { token: 0 } }, - true, - ); - } - }, - // CardCommentReactions at board level - { - async find(board) { - return await ReactiveCache.getCardCommentReactions({ boardId: board._id }, {}, true); - } - }, - // CustomFields - { - async find(board) { - return await ReactiveCache.getCustomFields( - { boardIds: { $in: [board._id] } }, - { sort: { name: 1 } }, - true, - ); - } - }, - // Cards - { - async find(board) { - const cardSelector = { - boardId: { $in: [board._id, board.subtasksDefaultBoardId] }, + + // Cards and cards comments + // XXX Originally we were publishing the card documents as a child of the + // list publication defined above using the following selector `{ listId: + // list._id }`. But it was causing a race condition in publish-composite, + // that I documented here: + // + // https://github.com/englue/meteor-publish-composite/issues/29 + // + // cottz:publish had a similar problem: + // + // https://github.com/Goluis/cottz-publish/issues/4 + // + // The current state of relational publishing in meteor is a bit sad, + // there are a lot of various packages, with various APIs, some of them + // are unmaintained. Fortunately this is something that will be fixed by + // meteor-core at some point: + // + // https://trello.com/c/BGvIwkEa/48-easy-joins-in-subscriptions + // + // And in the meantime our code below works pretty well -- it's not even a + // hack! + + // Gather queries and send in bulk + const cardComments = this.join(CardComments); + cardComments.selector = _ids => ({ cardId: _ids }); + const cardCommentsLinkedBoard = this.join(CardComments); + cardCommentsLinkedBoard.selector = _ids => ({ boardId: _ids }); + const cardCommentReactions = this.join(CardCommentReactions); + cardCommentReactions.selector = _ids => ({ cardId: _ids }); + const attachments = this.join(Attachments.collection); + attachments.selector = _ids => ({ 'meta.cardId': _ids }); + const checklists = this.join(Checklists); + checklists.selector = _ids => ({ cardId: _ids }); + const checklistItems = this.join(ChecklistItems); + checklistItems.selector = _ids => ({ cardId: _ids }); + const parentCards = this.join(Cards); + parentCards.selector = _ids => ({ parentId: _ids }); + const boards = this.join(Boards); + const subCards = this.join(Cards); + subCards.selector = _ids => ({ _id: _ids, archived: isArchived }); + const linkedBoardCards = this.join(Cards); + linkedBoardCards.selector = _ids => ({ boardId: _ids }); + + this.cursor( + ReactiveCache.getCards({ + boardId: { $in: [boardId, board.subtasksDefaultBoardId] }, archived: isArchived, - }; - - // Check if current user has assigned-only permissions - if (thisUserId && board.members) { - const member = _.findWhere(board.members, { userId: thisUserId, isActive: true }); - if (member && (member.isNormalAssignedOnly || member.isCommentAssignedOnly || member.isReadAssignedOnly)) { - // User with assigned-only permissions should only see cards assigned to them - cardSelector.assignees = { $in: [thisUserId] }; - } + }, + {}, + true, + ), + function(cardId, card) { + if (card.type === 'cardType-linkedCard') { + const impCardId = card.linkedId; + subCards.push(impCardId); // GitHub issue #2688 and #2693 + cardComments.push(impCardId); + attachments.push(impCardId); + checklists.push(impCardId); + checklistItems.push(impCardId); + } else if (card.type === 'cardType-linkedBoard') { + boards.push(card.linkedId); + linkedBoardCards.push(card.linkedId); + cardCommentsLinkedBoard.push(card.linkedId); } + cardComments.push(cardId); + attachments.push(cardId); + checklists.push(cardId); + checklistItems.push(cardId); + parentCards.push(cardId); + cardCommentReactions.push(cardId); + }, + ); - return await ReactiveCache.getCards(cardSelector, {}, true); - } - }, - // Batch CardComments for all cards - { - async find(board) { - const cardSelector = { - boardId: { $in: [board._id, board.subtasksDefaultBoardId] }, - archived: isArchived, - }; + // Send bulk queries for all found ids + subCards.send(); + cardComments.send(); + cardCommentReactions.send(); + attachments.send(); + checklists.send(); + checklistItems.send(); + boards.send(); + parentCards.send(); + linkedBoardCards.send(); + cardCommentsLinkedBoard.send(); - if (thisUserId && board.members) { - const member = _.findWhere(board.members, { userId: thisUserId, isActive: true }); - if (member && (member.isNormalAssignedOnly || member.isCommentAssignedOnly || member.isReadAssignedOnly)) { - cardSelector.assignees = { $in: [thisUserId] }; - } - } + if (board.members) { + // Board members. This publication also includes former board members that + // aren't members anymore but may have some activities attached to them in + // the history. + const memberIds = _.pluck(board.members, 'userId'); - const cards = await ReactiveCache.getCards(cardSelector, { fields: { _id: 1 } }, false); - if (!cards || cards.length === 0) return null; - - const cardIds = cards.map(c => c._id); - return await ReactiveCache.getCardComments({ cardId: { $in: cardIds } }, {}, true); - } - }, - // Batch Attachments for all cards - { - async find(board) { - const cardSelector = { - boardId: { $in: [board._id, board.subtasksDefaultBoardId] }, - archived: isArchived, - }; - - if (thisUserId && board.members) { - const member = _.findWhere(board.members, { userId: thisUserId, isActive: true }); - if (member && (member.isNormalAssignedOnly || member.isCommentAssignedOnly || member.isReadAssignedOnly)) { - cardSelector.assignees = { $in: [thisUserId] }; - } - } - - const cards = await ReactiveCache.getCards(cardSelector, { fields: { _id: 1 } }, false); - if (!cards || cards.length === 0) return null; - - const cardIds = cards.map(c => c._id); - const result = await ReactiveCache.getAttachments({ 'meta.cardId': { $in: cardIds } }, {}, true); - // Handle both cursor and direct return - return result.cursor || result; - } - }, - // Batch Checklists for all cards - { - async find(board) { - const cardSelector = { - boardId: { $in: [board._id, board.subtasksDefaultBoardId] }, - archived: isArchived, - }; - - if (thisUserId && board.members) { - const member = _.findWhere(board.members, { userId: thisUserId, isActive: true }); - if (member && (member.isNormalAssignedOnly || member.isCommentAssignedOnly || member.isReadAssignedOnly)) { - cardSelector.assignees = { $in: [thisUserId] }; - } - } - - const cards = await ReactiveCache.getCards(cardSelector, { fields: { _id: 1 } }, false); - if (!cards || cards.length === 0) return null; - - const cardIds = cards.map(c => c._id); - return await ReactiveCache.getChecklists({ cardId: { $in: cardIds } }, {}, true); - } - }, - // Batch ChecklistItems for all cards - { - async find(board) { - const cardSelector = { - boardId: { $in: [board._id, board.subtasksDefaultBoardId] }, - archived: isArchived, - }; - - if (thisUserId && board.members) { - const member = _.findWhere(board.members, { userId: thisUserId, isActive: true }); - if (member && (member.isNormalAssignedOnly || member.isCommentAssignedOnly || member.isReadAssignedOnly)) { - cardSelector.assignees = { $in: [thisUserId] }; - } - } - - const cards = await ReactiveCache.getCards(cardSelector, { fields: { _id: 1 } }, false); - if (!cards || cards.length === 0) return null; - - const cardIds = cards.map(c => c._id); - return await ReactiveCache.getChecklistItems({ cardId: { $in: cardIds } }, {}, true); - } - }, - // Parent cards (for subtasks) - { - async find(board) { - const cardSelector = { - boardId: { $in: [board._id, board.subtasksDefaultBoardId] }, - archived: isArchived, - }; - - if (thisUserId && board.members) { - const member = _.findWhere(board.members, { userId: thisUserId, isActive: true }); - if (member && (member.isNormalAssignedOnly || member.isCommentAssignedOnly || member.isReadAssignedOnly)) { - cardSelector.assignees = { $in: [thisUserId] }; - } - } - - const cards = await ReactiveCache.getCards(cardSelector, { fields: { _id: 1, parentId: 1 } }, false); - if (!cards || cards.length === 0) return null; - - const parentIds = cards.filter(c => c.parentId).map(c => c.parentId); - if (parentIds.length === 0) return null; - - return await ReactiveCache.getCards({ _id: { $in: parentIds } }, {}, true); - } - }, - // Linked cards (cardType-linkedCard) - { - async find(board) { - const cardSelector = { - boardId: { $in: [board._id, board.subtasksDefaultBoardId] }, - archived: isArchived, - }; - - if (thisUserId && board.members) { - const member = _.findWhere(board.members, { userId: thisUserId, isActive: true }); - if (member && (member.isNormalAssignedOnly || member.isCommentAssignedOnly || member.isReadAssignedOnly)) { - cardSelector.assignees = { $in: [thisUserId] }; - } - } - - const cards = await ReactiveCache.getCards(cardSelector, { fields: { _id: 1, type: 1, linkedId: 1 } }, false); - if (!cards || cards.length === 0) return null; - - const linkedCardIds = cards.filter(c => c.type === 'cardType-linkedCard' && c.linkedId).map(c => c.linkedId); - if (linkedCardIds.length === 0) return null; - - return await ReactiveCache.getCards({ _id: { $in: linkedCardIds }, archived: isArchived }, {}, true); - } - }, - // Comments for linked cards - { - async find(board) { - const cardSelector = { - boardId: { $in: [board._id, board.subtasksDefaultBoardId] }, - archived: isArchived, - }; - - if (thisUserId && board.members) { - const member = _.findWhere(board.members, { userId: thisUserId, isActive: true }); - if (member && (member.isNormalAssignedOnly || member.isCommentAssignedOnly || member.isReadAssignedOnly)) { - cardSelector.assignees = { $in: [thisUserId] }; - } - } - - const cards = await ReactiveCache.getCards(cardSelector, { fields: { _id: 1, type: 1, linkedId: 1 } }, false); - if (!cards || cards.length === 0) return null; - - const linkedCardIds = cards.filter(c => c.type === 'cardType-linkedCard' && c.linkedId).map(c => c.linkedId); - if (linkedCardIds.length === 0) return null; - - return await ReactiveCache.getCardComments({ cardId: { $in: linkedCardIds } }, {}, true); - } - }, - // Attachments for linked cards - { - async find(board) { - const cardSelector = { - boardId: { $in: [board._id, board.subtasksDefaultBoardId] }, - archived: isArchived, - }; - - if (thisUserId && board.members) { - const member = _.findWhere(board.members, { userId: thisUserId, isActive: true }); - if (member && (member.isNormalAssignedOnly || member.isCommentAssignedOnly || member.isReadAssignedOnly)) { - cardSelector.assignees = { $in: [thisUserId] }; - } - } - - const cards = await ReactiveCache.getCards(cardSelector, { fields: { _id: 1, type: 1, linkedId: 1 } }, false); - if (!cards || cards.length === 0) return null; - - const linkedCardIds = cards.filter(c => c.type === 'cardType-linkedCard' && c.linkedId).map(c => c.linkedId); - if (linkedCardIds.length === 0) return null; - - const result = await ReactiveCache.getAttachments({ 'meta.cardId': { $in: linkedCardIds } }, {}, true); - return result.cursor || result; - } - }, - // Checklists for linked cards - { - async find(board) { - const cardSelector = { - boardId: { $in: [board._id, board.subtasksDefaultBoardId] }, - archived: isArchived, - }; - - if (thisUserId && board.members) { - const member = _.findWhere(board.members, { userId: thisUserId, isActive: true }); - if (member && (member.isNormalAssignedOnly || member.isCommentAssignedOnly || member.isReadAssignedOnly)) { - cardSelector.assignees = { $in: [thisUserId] }; - } - } - - const cards = await ReactiveCache.getCards(cardSelector, { fields: { _id: 1, type: 1, linkedId: 1 } }, false); - if (!cards || cards.length === 0) return null; - - const linkedCardIds = cards.filter(c => c.type === 'cardType-linkedCard' && c.linkedId).map(c => c.linkedId); - if (linkedCardIds.length === 0) return null; - - return await ReactiveCache.getChecklists({ cardId: { $in: linkedCardIds } }, {}, true); - } - }, - // ChecklistItems for linked cards - { - async find(board) { - const cardSelector = { - boardId: { $in: [board._id, board.subtasksDefaultBoardId] }, - archived: isArchived, - }; - - if (thisUserId && board.members) { - const member = _.findWhere(board.members, { userId: thisUserId, isActive: true }); - if (member && (member.isNormalAssignedOnly || member.isCommentAssignedOnly || member.isReadAssignedOnly)) { - cardSelector.assignees = { $in: [thisUserId] }; - } - } - - const cards = await ReactiveCache.getCards(cardSelector, { fields: { _id: 1, type: 1, linkedId: 1 } }, false); - if (!cards || cards.length === 0) return null; - - const linkedCardIds = cards.filter(c => c.type === 'cardType-linkedCard' && c.linkedId).map(c => c.linkedId); - if (linkedCardIds.length === 0) return null; - - return await ReactiveCache.getChecklistItems({ cardId: { $in: linkedCardIds } }, {}, true); - } - }, - // Board members/Users - { - async find(board) { - if (board.members) { - // Board members. This publication also includes former board members that - // aren't members anymore but may have some activities attached to them in - // the history. - const memberIds = _.pluck(board.members, 'userId'); - - // We omit the current user because the client should already have that data, - // and sending it triggers a subtle bug: - // https://github.com/wefork/wekan/issues/15 - return await ReactiveCache.getUsers( - { - _id: { $in: _.without(memberIds, thisUserId) }, + // We omit the current user because the client should already have that data, + // and sending it triggers a subtle bug: + // https://github.com/wefork/wekan/issues/15 + this.cursor( + ReactiveCache.getUsers( + { + _id: { $in: _.without(memberIds, thisUserId) }, + }, + { + fields: { + username: 1, + 'profile.fullname': 1, + 'profile.avatarUrl': 1, + 'profile.initials': 1, }, - { - fields: { - username: 1, - 'profile.fullname': 1, - 'profile.avatarUrl': 1, - 'profile.initials': 1, - }, - }, - true, - ); - } - return null; - } + }, + true, + ), + ); + + //this.cursor(presences.find({ userId: { $in: memberIds } })); } - ] - }; + }, + ); + + const ret = this.ready(); + return ret; }); Meteor.methods({ - async copyBoard(boardId, properties) { + copyBoard(boardId, properties) { check(boardId, String); check(properties, Object); let ret = null; - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (board) { for (const key in properties) { board[key] = properties[key]; diff --git a/server/publications/cards.js b/server/publications/cards.js index 59ee2ee01..b0b0cb8c8 100644 --- a/server/publications/cards.js +++ b/server/publications/cards.js @@ -1,26 +1,25 @@ import { ReactiveCache } from '/imports/reactiveCache'; -import { publishComposite } from 'meteor/reywood:publish-composite'; import escapeForRegex from 'escape-string-regexp'; import Users from '../../models/users'; -import { - formatDateTime, - formatDate, - formatTime, - getISOWeek, - isValidDate, - isBefore, - isAfter, - isSame, - add, - subtract, - startOf, - endOf, - format, - parseDate, - now, - createDate, - fromNow, - calendar +import { + formatDateTime, + formatDate, + formatTime, + getISOWeek, + isValidDate, + isBefore, + isAfter, + isSame, + add, + subtract, + startOf, + endOf, + format, + parseDate, + now, + createDate, + fromNow, + calendar } from '/imports/lib/dateUtils'; import Boards from '../../models/boards'; import Lists from '../../models/lists'; @@ -74,33 +73,9 @@ import { CARD_TYPES } from '../../config/const'; import Org from "../../models/org"; import Team from "../../models/team"; -Meteor.publish('card', async cardId => { +Meteor.publish('card', cardId => { check(cardId, String); - - const userId = Meteor.userId(); - const card = await ReactiveCache.getCard({ _id: cardId }); - - if (!card || !card.boardId) { - return []; - } - - const board = await ReactiveCache.getBoard({ _id: card.boardId }); - if (!board || !board.isVisibleBy(userId)) { - return []; - } - - // If user has assigned-only permissions, check if they're assigned to this card - if (userId && board.members) { - const member = _.findWhere(board.members, { userId: userId, isActive: true }); - if (member && (member.isNormalAssignedOnly || member.isCommentAssignedOnly || member.isReadAssignedOnly)) { - // User with assigned-only permissions can only view cards assigned to them - if (!card.assignees || !card.assignees.includes(userId)) { - return []; // Don't publish if user is not assigned - } - } - } - - const ret = await ReactiveCache.getCards( + const ret = ReactiveCache.getCards( { _id: cardId }, {}, true, @@ -111,56 +86,28 @@ Meteor.publish('card', async cardId => { /** publish all data which is necessary to display card details as popup * @returns array of cursors */ -publishComposite('popupCardData', async function(cardId) { +Meteor.publishRelations('popupCardData', function(cardId) { check(cardId, String); - - const userId = this.userId; - const card = await ReactiveCache.getCard({ _id: cardId }); - - if (!card || !card.boardId) { - return []; - } - - const board = await ReactiveCache.getBoard({ _id: card.boardId }); - if (!board || !board.isVisibleBy(userId)) { - return []; - } - - // If user has assigned-only permissions, check if they're assigned to this card - if (userId && board.members) { - const member = _.findWhere(board.members, { userId: userId, isActive: true }); - if (member && (member.isNormalAssignedOnly || member.isCommentAssignedOnly || member.isReadAssignedOnly)) { - // User with assigned-only permissions can only view cards assigned to them - if (!card.assignees || !card.assignees.includes(userId)) { - return []; // Don't publish if user is not assigned - } - } - } - - return { - async find() { - return await ReactiveCache.getCards({ _id: cardId }, {}, true); + this.cursor( + ReactiveCache.getCards( + { _id: cardId }, + {}, + true, + ), + function(cardId, card) { + this.cursor(ReactiveCache.getBoards({_id: card.boardId}, {}, true)); + this.cursor(ReactiveCache.getLists({boardId: card.boardId}, {}, true)); }, - children: [ - { - async find(card) { - return await ReactiveCache.getBoards({ _id: card.boardId }, {}, true); - } - }, - { - async find(card) { - return await ReactiveCache.getLists({ boardId: card.boardId }, {}, true); - } - } - ] - }; + ); + const ret = this.ready() + return ret; }); -Meteor.publish('myCards', async function(sessionId) { +Meteor.publish('myCards', function(sessionId) { check(sessionId, String); const queryParams = new QueryParams(); - queryParams.addPredicate(OPERATOR_USER, (await ReactiveCache.getCurrentUser()).username); + queryParams.addPredicate(OPERATOR_USER, ReactiveCache.getCurrentUser().username); queryParams.setPredicate(OPERATOR_LIMIT, 200); const query = buildQuery(queryParams); @@ -175,9 +122,9 @@ Meteor.publish('myCards', async function(sessionId) { }); // Optimized due cards publication for better performance -Meteor.publish('dueCards', async function(allUsers = false) { +Meteor.publish('dueCards', function(allUsers = false) { check(allUsers, Boolean); - + const userId = this.userId; if (!userId) { return this.ready(); @@ -188,17 +135,17 @@ Meteor.publish('dueCards', async function(allUsers = false) { } // Get user's board memberships for efficient filtering - const userBoards = (await ReactiveCache.getBoards({ + const userBoards = ReactiveCache.getBoards({ $or: [ { permission: 'public' }, { members: { $elemMatch: { userId, isActive: true } } } ] - })).map(board => board._id); + }).map(board => board._id); if (process.env.DEBUG === 'true') { console.log('dueCards userBoards:', userBoards); console.log('dueCards userBoards count:', userBoards.length); - + // Also check if there are any cards with due dates in the system at all const allCardsWithDueDates = Cards.find({ type: 'cardType-card', @@ -255,7 +202,7 @@ Meteor.publish('dueCards', async function(allUsers = false) { } const result = Cards.find(selector, options); - + if (process.env.DEBUG === 'true') { const count = result.count(); console.log('dueCards publication: returning', count, 'cards'); @@ -273,7 +220,7 @@ Meteor.publish('dueCards', async function(allUsers = false) { return result; }); -Meteor.publish('globalSearch', async function(sessionId, params, text) { +Meteor.publish('globalSearch', function(sessionId, params, text) { check(sessionId, String); check(params, Object); check(text, String); @@ -282,7 +229,7 @@ Meteor.publish('globalSearch', async function(sessionId, params, text) { console.log('globalSearch publication called with:', { sessionId, params, text }); } - const ret = findCards(sessionId, await buildQuery(new QueryParams(params, text))); + const ret = findCards(sessionId, buildQuery(new QueryParams(params, text))); if (process.env.DEBUG === 'true') { console.log('globalSearch publication returning:', ret); } @@ -295,7 +242,7 @@ Meteor.publish('sessionData', function(sessionId) { if (process.env.DEBUG === 'true') { console.log('sessionData publication called with:', { sessionId, userId }); } - + const cursor = SessionData.find({ userId, sessionId }); if (process.env.DEBUG === 'true') { console.log('sessionData publication returning cursor with count:', cursor.count()); @@ -303,7 +250,7 @@ Meteor.publish('sessionData', function(sessionId) { return cursor; }); -async function buildSelector(queryParams) { +function buildSelector(queryParams) { const userId = Meteor.userId(); const errors = new QueryErrors(); @@ -336,8 +283,8 @@ async function buildSelector(queryParams) { if (queryParams.hasOperator(OPERATOR_ORG)) { const orgs = []; - for (const name of queryParams.getPredicates(OPERATOR_ORG)) { - const org = await ReactiveCache.getOrg({ + queryParams.getPredicates(OPERATOR_ORG).forEach(name => { + const org = ReactiveCache.getOrg({ $or: [ { orgDisplayName: name }, { orgShortName: name } @@ -348,7 +295,7 @@ async function buildSelector(queryParams) { } else { errors.addNotFound(OPERATOR_ORG, name); } - } + }); if (orgs.length) { boardsSelector.orgs = { $elemMatch: { orgId: { $in: orgs }, isActive: true } @@ -358,8 +305,8 @@ async function buildSelector(queryParams) { if (queryParams.hasOperator(OPERATOR_TEAM)) { const teams = []; - for (const name of queryParams.getPredicates(OPERATOR_TEAM)) { - const team = await ReactiveCache.getTeam({ + queryParams.getPredicates(OPERATOR_TEAM).forEach(name => { + const team = ReactiveCache.getTeam({ $or: [ { teamDisplayName: name }, { teamShortName: name } @@ -370,7 +317,7 @@ async function buildSelector(queryParams) { } else { errors.addNotFound(OPERATOR_TEAM, name); } - } + }); if (teams.length) { boardsSelector.teams = { $elemMatch: { teamId: { $in: teams }, isActive: true } @@ -387,13 +334,13 @@ async function buildSelector(queryParams) { if (archived !== null) { if (archived) { selector.boardId = { - $in: await Boards.userBoardIds(userId, null, boardsSelector), + $in: Boards.userBoardIds(userId, null, boardsSelector), }; selector.$and.push({ $or: [ { boardId: { - $in: await Boards.userBoardIds(userId, archived, boardsSelector), + $in: Boards.userBoardIds(userId, archived, boardsSelector), }, }, { swimlaneId: { $in: Swimlanes.userArchivedSwimlaneIds(userId) } }, @@ -403,14 +350,14 @@ async function buildSelector(queryParams) { }); } else { selector.boardId = { - $in: await Boards.userBoardIds(userId, false, boardsSelector), + $in: Boards.userBoardIds(userId, false, boardsSelector), }; selector.swimlaneId = { $nin: Swimlanes.archivedSwimlaneIds() }; selector.listId = { $nin: Lists.archivedListIds() }; selector.archived = false; } } else { - const userBoardIds = await Boards.userBoardIds(userId, null, boardsSelector); + const userBoardIds = Boards.userBoardIds(userId, null, boardsSelector); if (process.env.DEBUG === 'true') { console.log('buildSelector - userBoardIds:', userBoardIds); } @@ -424,8 +371,8 @@ async function buildSelector(queryParams) { if (queryParams.hasOperator(OPERATOR_BOARD)) { const queryBoards = []; - for (const query of queryParams.getPredicates(OPERATOR_BOARD)) { - const boards = await Boards.userSearch(userId, { + queryParams.getPredicates(OPERATOR_BOARD).forEach(query => { + const boards = Boards.userSearch(userId, { title: new RegExp(escapeForRegex(query), 'i'), }); if (boards.length) { @@ -435,15 +382,15 @@ async function buildSelector(queryParams) { } else { errors.addNotFound(OPERATOR_BOARD, query); } - } + }); selector.boardId.$in = queryBoards; } if (queryParams.hasOperator(OPERATOR_SWIMLANE)) { const querySwimlanes = []; - for (const query of queryParams.getPredicates(OPERATOR_SWIMLANE)) { - const swimlanes = await ReactiveCache.getSwimlanes({ + queryParams.getPredicates(OPERATOR_SWIMLANE).forEach(query => { + const swimlanes = ReactiveCache.getSwimlanes({ title: new RegExp(escapeForRegex(query), 'i'), }); if (swimlanes.length) { @@ -453,7 +400,7 @@ async function buildSelector(queryParams) { } else { errors.addNotFound(OPERATOR_SWIMLANE, query); } - } + }); // eslint-disable-next-line no-prototype-builtins if (!selector.swimlaneId.hasOwnProperty('swimlaneId')) { @@ -464,8 +411,8 @@ async function buildSelector(queryParams) { if (queryParams.hasOperator(OPERATOR_LIST)) { const queryLists = []; - for (const query of queryParams.getPredicates(OPERATOR_LIST)) { - const lists = await ReactiveCache.getLists({ + queryParams.getPredicates(OPERATOR_LIST).forEach(query => { + const lists = ReactiveCache.getLists({ title: new RegExp(escapeForRegex(query), 'i'), }); if (lists.length) { @@ -475,7 +422,7 @@ async function buildSelector(queryParams) { } else { errors.addNotFound(OPERATOR_LIST, query); } - } + }); // eslint-disable-next-line no-prototype-builtins if (!selector.hasOwnProperty('listId')) { @@ -516,14 +463,14 @@ async function buildSelector(queryParams) { if (queryParams.hasOperator(OPERATOR_USER)) { const users = []; - for (const username of queryParams.getPredicates(OPERATOR_USER)) { - const user = await ReactiveCache.getUser({ username }); + queryParams.getPredicates(OPERATOR_USER).forEach(username => { + const user = ReactiveCache.getUser({ username }); if (user) { users.push(user._id); } else { errors.addNotFound(OPERATOR_USER, username); } - } + }); if (users.length) { selector.$and.push({ $or: [{ members: { $in: users } }, { assignees: { $in: users } }], @@ -531,32 +478,36 @@ async function buildSelector(queryParams) { } } - for (const key of [OPERATOR_MEMBER, OPERATOR_ASSIGNEE, OPERATOR_CREATOR]) { + [OPERATOR_MEMBER, OPERATOR_ASSIGNEE, OPERATOR_CREATOR].forEach(key => { if (queryParams.hasOperator(key)) { const users = []; - for (const username of queryParams.getPredicates(key)) { - const user = await ReactiveCache.getUser({ username }); + queryParams.getPredicates(key).forEach(username => { + const user = ReactiveCache.getUser({ username }); if (user) { users.push(user._id); } else { errors.addNotFound(key, username); } - } + }); if (users.length) { selector[key] = { $in: users }; } } - } + }); if (queryParams.hasOperator(OPERATOR_LABEL)) { const queryLabels = []; - for (const label of queryParams.getPredicates(OPERATOR_LABEL)) { - let boards = await Boards.userBoards(userId, null, { + queryParams.getPredicates(OPERATOR_LABEL).forEach(label => { + let boards = Boards.userBoards(userId, null, { labels: { $elemMatch: { color: label.toLowerCase() } }, }); if (boards.length) { boards.forEach(board => { + // eslint-disable-next-line no-console + // console.log('board:', board); + // eslint-disable-next-line no-console + // console.log('board.labels:', board.labels); board.labels .filter(boardLabel => { return boardLabel.color === label.toLowerCase(); @@ -566,8 +517,12 @@ async function buildSelector(queryParams) { }); }); } else { + // eslint-disable-next-line no-console + // console.log('label:', label); const reLabel = new RegExp(escapeForRegex(label), 'i'); - boards = await Boards.userBoards(userId, null, { + // eslint-disable-next-line no-console + // console.log('reLabel:', reLabel); + boards = Boards.userBoards(userId, null, { labels: { $elemMatch: { name: reLabel } }, }); @@ -588,7 +543,7 @@ async function buildSelector(queryParams) { errors.addNotFound(OPERATOR_LABEL, label); } } - } + }); if (queryLabels.length) { // eslint-disable-next-line no-console // console.log('queryLabels:', queryLabels); @@ -597,12 +552,12 @@ async function buildSelector(queryParams) { } if (queryParams.hasOperator(OPERATOR_HAS)) { - for (const has of queryParams.getPredicates(OPERATOR_HAS)) { + queryParams.getPredicates(OPERATOR_HAS).forEach(has => { switch (has.field) { case PREDICATE_ATTACHMENT: selector.$and.push({ _id: { - $in: (await ReactiveCache.getAttachments({}, { fields: { cardId: 1 } })).map( + $in: ReactiveCache.getAttachments({}, { fields: { cardId: 1 } }).map( a => a.cardId, ), }, @@ -611,7 +566,7 @@ async function buildSelector(queryParams) { case PREDICATE_CHECKLIST: selector.$and.push({ _id: { - $in: (await ReactiveCache.getChecklists({}, { fields: { cardId: 1 } })).map( + $in: ReactiveCache.getChecklists({}, { fields: { cardId: 1 } }).map( a => a.cardId, ), }, @@ -636,17 +591,17 @@ async function buildSelector(queryParams) { } break; } - } + }); } if (queryParams.text) { const regex = new RegExp(escapeForRegex(queryParams.text), 'i'); - const items = await ReactiveCache.getChecklistItems( + const items = ReactiveCache.getChecklistItems( { title: regex }, { fields: { cardId: 1, checklistId: 1 } }, ); - const checklists = await ReactiveCache.getChecklists( + const checklists = ReactiveCache.getChecklists( { $or: [ { title: regex }, @@ -656,9 +611,9 @@ async function buildSelector(queryParams) { { fields: { cardId: 1 } }, ); - const attachments = await ReactiveCache.getAttachments({ 'original.name': regex }); + const attachments = ReactiveCache.getAttachments({ 'original.name': regex }); - const comments = await ReactiveCache.getCardComments( + const comments = ReactiveCache.getCardComments( { text: regex }, { fields: { cardId: 1 } }, ); @@ -798,18 +753,18 @@ function buildProjection(query) { return query; } -async function buildQuery(queryParams) { - const query = await buildSelector(queryParams); +function buildQuery(queryParams) { + const query = buildSelector(queryParams); return buildProjection(query); } -Meteor.publish('brokenCards', async function(sessionId) { +Meteor.publish('brokenCards', function(sessionId) { check(sessionId, String); const params = new QueryParams(); params.addPredicate(OPERATOR_STATUS, PREDICATE_ALL); - const query = await buildQuery(params); + const query = buildQuery(params); query.selector.$or = [ { boardId: { $in: [null, ''] } }, { swimlaneId: { $in: [null, ''] } }, @@ -822,10 +777,10 @@ Meteor.publish('brokenCards', async function(sessionId) { return ret; }); -Meteor.publish('nextPage', async function(sessionId) { +Meteor.publish('nextPage', function(sessionId) { check(sessionId, String); - const session = await ReactiveCache.getSessionData({ sessionId }); + const session = ReactiveCache.getSessionData({ sessionId }); const projection = session.getProjection(); projection.skip = session.lastHit; @@ -833,10 +788,10 @@ Meteor.publish('nextPage', async function(sessionId) { return ret; }); -Meteor.publish('previousPage', async function(sessionId) { +Meteor.publish('previousPage', function(sessionId) { check(sessionId, String); - const session = await ReactiveCache.getSessionData({ sessionId }); + const session = ReactiveCache.getSessionData({ sessionId }); const projection = session.getProjection(); projection.skip = session.lastHit - session.resultsCount - projection.limit; @@ -844,7 +799,7 @@ Meteor.publish('previousPage', async function(sessionId) { return ret; }); -async function findCards(sessionId, query) { +function findCards(sessionId, query) { const userId = Meteor.userId(); // eslint-disable-next-line no-console @@ -855,7 +810,7 @@ async function findCards(sessionId, query) { console.log('findCards - projection:', query.projection); } - const cards = await ReactiveCache.getCards(query.selector, query.projection, true); + const cards = ReactiveCache.getCards(query.selector, query.projection, true); if (process.env.DEBUG === 'true') { console.log('findCards - cards count:', cards ? cards.count() : 0); } @@ -895,7 +850,7 @@ async function findCards(sessionId, query) { if (process.env.DEBUG === 'true') { console.log('findCards - upsertResult:', upsertResult); } - + // Check if the session data was actually stored const storedSessionData = SessionData.findOne({ userId, sessionId }); if (process.env.DEBUG === 'true') { @@ -960,7 +915,7 @@ async function findCards(sessionId, query) { console.log('findCards - session data count (after delay):', sessionDataCursor.count()); } }, 100); - + const sessionDataCursor = SessionData.find({ userId, sessionId }); if (process.env.DEBUG === 'true') { console.log('findCards - publishing session data cursor:', sessionDataCursor); @@ -969,23 +924,23 @@ async function findCards(sessionId, query) { return [ cards, - await ReactiveCache.getBoards( + ReactiveCache.getBoards( { _id: { $in: boards } }, { fields: { ...fields, labels: 1, color: 1 } }, true, ), - await ReactiveCache.getSwimlanes( + ReactiveCache.getSwimlanes( { _id: { $in: swimlanes } }, { fields: { ...fields, color: 1 } }, true, ), - await ReactiveCache.getLists({ _id: { $in: lists } }, { fields }, true), - await ReactiveCache.getCustomFields({ _id: { $in: customFieldIds } }, {}, true), - await ReactiveCache.getUsers({ _id: { $in: users } }, { fields: Users.safeFields }, true), - await ReactiveCache.getChecklists({ cardId: { $in: cards.map(c => c._id) } }, {}, true), - await ReactiveCache.getChecklistItems({ cardId: { $in: cards.map(c => c._id) } }, {}, true), - (await ReactiveCache.getAttachments({ 'meta.cardId': { $in: cards.map(c => c._id) } }, {}, true)).cursor, - await ReactiveCache.getCardComments({ cardId: { $in: cards.map(c => c._id) } }, {}, true), + ReactiveCache.getLists({ _id: { $in: lists } }, { fields }, true), + ReactiveCache.getCustomFields({ _id: { $in: customFieldIds } }, {}, true), + ReactiveCache.getUsers({ _id: { $in: users } }, { fields: Users.safeFields }, true), + ReactiveCache.getChecklists({ cardId: { $in: cards.map(c => c._id) } }, {}, true), + ReactiveCache.getChecklistItems({ cardId: { $in: cards.map(c => c._id) } }, {}, true), + ReactiveCache.getAttachments({ 'meta.cardId': { $in: cards.map(c => c._id) } }, {}, true).cursor, + ReactiveCache.getCardComments({ cardId: { $in: cards.map(c => c._id) } }, {}, true), sessionDataCursor, ]; } diff --git a/server/publications/cronJobs.js b/server/publications/cronJobs.js deleted file mode 100644 index 1c9bdb4e6..000000000 --- a/server/publications/cronJobs.js +++ /dev/null @@ -1,16 +0,0 @@ -import { CronJobStatus } from '/server/cronJobStorage'; - -// Publish cron jobs status for admin users only -Meteor.publish('cronJobs', function() { - if (!this.userId) { - return this.ready(); - } - - const user = Users.findOne(this.userId); - if (!user || !user.isAdmin) { - return this.ready(); - } - - // Publish all cron job status documents - return CronJobStatus.find({}); -}); diff --git a/server/publications/cronMigrationStatus.js b/server/publications/cronMigrationStatus.js deleted file mode 100644 index 76c0fb8d4..000000000 --- a/server/publications/cronMigrationStatus.js +++ /dev/null @@ -1,16 +0,0 @@ -import { CronJobStatus } from '../cronJobStorage'; - -// Publish migration status for admin users only -Meteor.publish('cronMigrationStatus', function() { - if (!this.userId) { - return this.ready(); - } - - const user = Users.findOne(this.userId); - if (!user || !user.isAdmin) { - return this.ready(); - } - - // Publish all cron job status documents - return CronJobStatus.find({}); -}); diff --git a/server/publications/customUI.js b/server/publications/customUI.js deleted file mode 100644 index 55f475648..000000000 --- a/server/publications/customUI.js +++ /dev/null @@ -1,29 +0,0 @@ -// Publish custom UI configuration -Meteor.publish('customUI', function() { - // Published to all users (public configuration) - return Settings.find({}, { - fields: { - customLoginLogoImageUrl: 1, - customLoginLogoLinkUrl: 1, - customHelpLinkUrl: 1, - textBelowCustomLoginLogo: 1, - customTopLeftCornerLogoImageUrl: 1, - customTopLeftCornerLogoLinkUrl: 1, - customTopLeftCornerLogoHeight: 1, - customHTMLafterBodyStart: 1, - customHTMLbeforeBodyEnd: 1, - } - }); -}); - -// Publish Matomo configuration -Meteor.publish('matomoConfig', function() { - // Published to all users (public configuration) - return Settings.find({}, { - fields: { - matomoEnabled: 1, - matomoURL: 1, - matomoSiteId: 1, - } - }); -}); diff --git a/server/publications/lockoutSettings.js b/server/publications/lockoutSettings.js index 089083f2a..c94309c33 100644 --- a/server/publications/lockoutSettings.js +++ b/server/publications/lockoutSettings.js @@ -1,5 +1,4 @@ import LockoutSettings from '/models/lockoutSettings'; -import { Settings } from '../../models/settings'; Meteor.publish('lockoutSettings', function() { const ret = LockoutSettings.find(); diff --git a/server/publications/migrationProgress.js b/server/publications/migrationProgress.js deleted file mode 100644 index ba1c90ee3..000000000 --- a/server/publications/migrationProgress.js +++ /dev/null @@ -1,22 +0,0 @@ -import { CronJobStatus } from '/server/cronJobStorage'; - -// Publish detailed migration progress data for admin users -Meteor.publish('migrationProgress', function() { - if (!this.userId) { - return this.ready(); - } - - const user = Users.findOne(this.userId); - if (!user || !user.isAdmin) { - return this.ready(); - } - - // Publish detailed migration progress documents - // This includes current running job details, estimated time, etc. - return CronJobStatus.find({ - $or: [ - { jobType: 'migration' }, - { jobId: 'migration' } - ] - }); -}); diff --git a/server/publications/notifications.js b/server/publications/notifications.js index 556f5948d..1d9db198e 100644 --- a/server/publications/notifications.js +++ b/server/publications/notifications.js @@ -3,33 +3,33 @@ import { ReactiveCache } from '/imports/reactiveCache'; // We use these when displaying notifications in the notificationsDrawer // gets all activities associated with the current user -Meteor.publish('notificationActivities', async () => { - const ret = await activities(); +Meteor.publish('notificationActivities', () => { + const ret = activities(); return ret; }); // gets all attachments associated with activities associated with the current user -Meteor.publish('notificationAttachments', async function() { - const ret = (await ReactiveCache.getAttachments( +Meteor.publish('notificationAttachments', function() { + const ret = ReactiveCache.getAttachments( { _id: { - $in: (await activities()) + $in: activities() .map(v => v.attachmentId) .filter(v => !!v), }, }, {}, true, - )).cursor; + ).cursor; return ret; }); // gets all cards associated with activities associated with the current user -Meteor.publish('notificationCards', async function() { - const ret = await ReactiveCache.getCards( +Meteor.publish('notificationCards', function() { + const ret = ReactiveCache.getCards( { _id: { - $in: (await activities()) + $in: activities() .map(v => v.cardId) .filter(v => !!v), }, @@ -41,11 +41,11 @@ Meteor.publish('notificationCards', async function() { }); // gets all checklistItems associated with activities associated with the current user -Meteor.publish('notificationChecklistItems', async function() { - const ret = await ReactiveCache.getChecklistItems( +Meteor.publish('notificationChecklistItems', function() { + const ret = ReactiveCache.getChecklistItems( { _id: { - $in: (await activities()) + $in: activities() .map(v => v.checklistItemId) .filter(v => !!v), }, @@ -57,11 +57,11 @@ Meteor.publish('notificationChecklistItems', async function() { }); // gets all checklists associated with activities associated with the current user -Meteor.publish('notificationChecklists', async function() { - const ret = await ReactiveCache.getChecklists( +Meteor.publish('notificationChecklists', function() { + const ret = ReactiveCache.getChecklists( { _id: { - $in: (await activities()) + $in: activities() .map(v => v.checklistId) .filter(v => !!v), }, @@ -73,11 +73,11 @@ Meteor.publish('notificationChecklists', async function() { }); // gets all comments associated with activities associated with the current user -Meteor.publish('notificationComments', async function() { - const ret = await ReactiveCache.getCardComments( +Meteor.publish('notificationComments', function() { + const ret = ReactiveCache.getCardComments( { _id: { - $in: (await activities()) + $in: activities() .map(v => v.commentId) .filter(v => !!v), }, @@ -89,11 +89,11 @@ Meteor.publish('notificationComments', async function() { }); // gets all lists associated with activities associated with the current user -Meteor.publish('notificationLists', async function() { - const ret = await ReactiveCache.getLists( +Meteor.publish('notificationLists', function() { + const ret = ReactiveCache.getLists( { _id: { - $in: (await activities()) + $in: activities() .map(v => v.listId) .filter(v => !!v), }, @@ -105,11 +105,11 @@ Meteor.publish('notificationLists', async function() { }); // gets all swimlanes associated with activities associated with the current user -Meteor.publish('notificationSwimlanes', async function() { - const ret = await ReactiveCache.getSwimlanes( +Meteor.publish('notificationSwimlanes', function() { + const ret = ReactiveCache.getSwimlanes( { _id: { - $in: (await activities()) + $in: activities() .map(v => v.swimlaneId) .filter(v => !!v), }, @@ -121,33 +121,26 @@ Meteor.publish('notificationSwimlanes', async function() { }); // gets all users associated with activities associated with the current user -Meteor.publish('notificationUsers', async function() { - const ret = await ReactiveCache.getUsers( +Meteor.publish('notificationUsers', function() { + const ret = ReactiveCache.getUsers( { _id: { - $in: (await activities()) + $in: activities() .map(v => v.userId) .filter(v => !!v), }, }, - { - fields: { - username: 1, - 'profile.fullname': 1, - 'profile.avatarUrl': 1, - 'profile.initials': 1, - }, - }, + {}, true, ); return ret; }); -async function activities() { - const activityIds = (await ReactiveCache.getCurrentUser())?.profile?.notifications?.map(v => v.activity) || []; +function activities() { + const activityIds = ReactiveCache.getCurrentUser()?.profile?.notifications?.map(v => v.activity) || []; let ret = []; if (activityIds.length > 0) { - ret = await ReactiveCache.getActivities( + ret = ReactiveCache.getActivities( { _id: { $in: activityIds }, }, diff --git a/server/publications/org.js b/server/publications/org.js index e7569a396..adf7f33d0 100644 --- a/server/publications/org.js +++ b/server/publications/org.js @@ -1,14 +1,14 @@ import { ReactiveCache } from '/imports/reactiveCache'; -Meteor.publish('org', async function(query, limit) { +Meteor.publish('org', function(query, limit) { check(query, Match.OneOf(Object, null)); check(limit, Number); let ret = []; - const user = await ReactiveCache.getCurrentUser(); + const user = ReactiveCache.getCurrentUser(); if (user && user.isAdmin) { - ret = await ReactiveCache.getOrgs(query, + ret = ReactiveCache.getOrgs(query, { limit, sort: { createdAt: -1 }, diff --git a/server/publications/people.js b/server/publications/people.js index 57602757f..7135cb820 100644 --- a/server/publications/people.js +++ b/server/publications/people.js @@ -1,14 +1,14 @@ import { ReactiveCache } from '/imports/reactiveCache'; -Meteor.publish('people', async function(query, limit) { +Meteor.publish('people', function(query, limit) { check(query, Match.OneOf(Object, null)); check(limit, Number); let ret = []; - const user = await ReactiveCache.getCurrentUser(); + const user = ReactiveCache.getCurrentUser(); if (user && user.isAdmin) { - ret = await ReactiveCache.getUsers(query, { + ret = ReactiveCache.getUsers(query, { limit, sort: { createdAt: -1 }, fields: { diff --git a/server/publications/rules.js b/server/publications/rules.js index 78c2f5896..9a68ed997 100644 --- a/server/publications/rules.js +++ b/server/publications/rules.js @@ -2,26 +2,10 @@ import Boards from '/models/boards'; import Actions from '/models/actions'; import Triggers from '/models/triggers'; import Rules from '/models/rules'; -import { ReactiveCache } from '/imports/reactiveCache'; -Meteor.publish('rules', async function(ruleId) { +Meteor.publish('rules', ruleId => { check(ruleId, String); - - if (!this.userId) { - return this.ready(); - } - - const rule = await ReactiveCache.getRule(ruleId); - if (!rule) { - return this.ready(); - } - - const board = await ReactiveCache.getBoard(rule.boardId); - if (!board || !board.isVisibleBy(this.userId)) { - return this.ready(); - } - - const ret = await ReactiveCache.getRules( + const ret = ReactiveCache.getRules( { _id: ruleId, }, @@ -31,39 +15,23 @@ Meteor.publish('rules', async function(ruleId) { return ret; }); -Meteor.publish('allRules', async function() { - if (!this.userId || !(await ReactiveCache.getUser(this.userId)).isAdmin) { - return this.ready(); - } - - const ret = await ReactiveCache.getRules({}, {}, true); +Meteor.publish('allRules', () => { + const ret = ReactiveCache.getRules({}, {}, true); return ret; }); -Meteor.publish('allTriggers', async function() { - if (!this.userId || !(await ReactiveCache.getUser(this.userId)).isAdmin) { - return this.ready(); - } - - const ret = await ReactiveCache.getTriggers({}, {}, true); +Meteor.publish('allTriggers', () => { + const ret = ReactiveCache.getTriggers({}, {}, true); return ret; }); -Meteor.publish('allActions', async function() { - if (!this.userId || !(await ReactiveCache.getUser(this.userId)).isAdmin) { - return this.ready(); - } - - const ret = await ReactiveCache.getActions({}, {}, true); +Meteor.publish('allActions', () => { + const ret = ReactiveCache.getActions({}, {}, true); return ret; }); -Meteor.publish('rulesReport', async function() { - if (!this.userId || !(await ReactiveCache.getUser(this.userId)).isAdmin) { - return this.ready(); - } - - const rules = await ReactiveCache.getRules({}, {}, true); +Meteor.publish('rulesReport', () => { + const rules = ReactiveCache.getRules({}, {}, true); const actionIds = []; const triggerIds = []; const boardIds = []; @@ -76,9 +44,9 @@ Meteor.publish('rulesReport', async function() { const ret = [ rules, - await ReactiveCache.getActions({ _id: { $in: actionIds } }, {}, true), - await ReactiveCache.getTriggers({ _id: { $in: triggerIds } }, {}, true), - await ReactiveCache.getBoards({ _id: { $in: boardIds } }, { fields: { title: 1 } }, true), + ReactiveCache.getActions({ _id: { $in: actionIds } }, {}, true), + ReactiveCache.getTriggers({ _id: { $in: triggerIds } }, {}, true), + ReactiveCache.getBoards({ _id: { $in: boardIds } }, { fields: { title: 1 } }, true), ]; return ret; }); diff --git a/server/publications/settings.js b/server/publications/settings.js index f832d0a99..e2365d523 100644 --- a/server/publications/settings.js +++ b/server/publications/settings.js @@ -1,25 +1,12 @@ import { ReactiveCache } from '/imports/reactiveCache'; -Meteor.publish('globalwebhooks', async function() { - if (!this.userId) { - return this.ready(); - } - - const user = await ReactiveCache.getCurrentUser(); - if (!user || !user.isAdmin) { - return this.ready(); - } - +Meteor.publish('globalwebhooks', () => { const boardId = Integrations.Const.GLOBAL_WEBHOOK_ID; - const ret = await ReactiveCache.getIntegrations( + const ret = ReactiveCache.getIntegrations( { boardId, }, - { - fields: { - token: 0, - }, - }, + {}, true, ); return ret; @@ -51,13 +38,6 @@ Meteor.publish('setting', () => { oidcBtnText: 1, mailDomainName: 1, legalNotice: 1, - customHeadEnabled: 1, - customHeadMetaTags: 1, - customHeadLinkTags: 1, - customManifestEnabled: 1, - customManifestContent: 1, - customAssetLinksEnabled: 1, - customAssetLinksContent: 1, accessibilityPageEnabled: 1, accessibilityTitle: 1, accessibilityContent: 1, @@ -67,8 +47,8 @@ Meteor.publish('setting', () => { return ret; }); -Meteor.publish('mailServer', async function() { - const user = await ReactiveCache.getCurrentUser(); +Meteor.publish('mailServer', function() { + const user = ReactiveCache.getCurrentUser(); let ret = [] if (user && user.isAdmin) { diff --git a/server/publications/swimlanes.js b/server/publications/swimlanes.js index dc8f295bf..ecf45021c 100644 --- a/server/publications/swimlanes.js +++ b/server/publications/swimlanes.js @@ -1,12 +1,12 @@ import { ReactiveCache } from '/imports/reactiveCache'; Meteor.methods({ - async copySwimlane(swimlaneId, toBoardId) { + copySwimlane(swimlaneId, toBoardId) { check(swimlaneId, String); check(toBoardId, String); - const swimlane = await ReactiveCache.getSwimlane(swimlaneId); - const toBoard = await ReactiveCache.getBoard(toBoardId); + const swimlane = ReactiveCache.getSwimlane(swimlaneId); + const toBoard = ReactiveCache.getBoard(toBoardId); let ret = false; if (swimlane && toBoard) { @@ -17,16 +17,16 @@ Meteor.methods({ return ret; }, - async moveSwimlane(swimlaneId, toBoardId) { + moveSwimlane(swimlaneId, toBoardId) { check(swimlaneId, String); check(toBoardId, String); - const swimlane = await ReactiveCache.getSwimlane(swimlaneId); - const toBoard = await ReactiveCache.getBoard(toBoardId); + const swimlane = ReactiveCache.getSwimlane(swimlaneId); + const toBoard = ReactiveCache.getBoard(toBoardId); let ret = false; if (swimlane && toBoard) { - await swimlane.move(toBoardId); + swimlane.move(toBoardId); ret = true; } diff --git a/server/publications/tableVisibilityModeSettings.js b/server/publications/tableVisibilityModeSettings.js index 9e04875c4..4326e59a6 100644 --- a/server/publications/tableVisibilityModeSettings.js +++ b/server/publications/tableVisibilityModeSettings.js @@ -1,5 +1,3 @@ -import { Settings } from '../../models/settings'; - Meteor.publish('tableVisibilityModeSettings', function() { const ret = TableVisibilityModeSettings.find(); return ret; diff --git a/server/publications/team.js b/server/publications/team.js index 6a8437c02..37a161793 100644 --- a/server/publications/team.js +++ b/server/publications/team.js @@ -1,14 +1,14 @@ import { ReactiveCache } from '/imports/reactiveCache'; -Meteor.publish('team', async function(query, limit) { +Meteor.publish('team', function(query, limit) { check(query, Match.OneOf(Object, null)); check(limit, Number); - const user = await ReactiveCache.getCurrentUser(); + const user = ReactiveCache.getCurrentUser(); let ret = []; if (user && user.isAdmin) { - ret = await ReactiveCache.getTeams(query, + ret = ReactiveCache.getTeams(query, { limit, sort: { createdAt: -1 }, diff --git a/server/publications/translation.js b/server/publications/translation.js index 09b79fb5d..2868329f0 100644 --- a/server/publications/translation.js +++ b/server/publications/translation.js @@ -1,14 +1,14 @@ import { ReactiveCache } from '/imports/reactiveCache'; -Meteor.publish('translation', async function(query, limit) { +Meteor.publish('translation', function(query, limit) { check(query, Match.OneOf(Object, null)); check(limit, Number); let ret = []; - const user = await ReactiveCache.getCurrentUser(); + const user = ReactiveCache.getCurrentUser(); if (user && user.isAdmin) { - ret = await ReactiveCache.getTranslations(query, + ret = ReactiveCache.getTranslations(query, { limit, sort: { modifiedAt: -1 }, diff --git a/server/publications/userDesktopDragHandles.js b/server/publications/userDesktopDragHandles.js deleted file mode 100644 index 3603ecaf6..000000000 --- a/server/publications/userDesktopDragHandles.js +++ /dev/null @@ -1,8 +0,0 @@ -Meteor.publish('userDesktopDragHandles', function() { - if (!this.userId) return this.ready(); - return Meteor.users.find({ _id: this.userId }, { - fields: { - 'profile.showDesktopDragHandles': 1 - } - }); -}); \ No newline at end of file diff --git a/server/publications/userGreyIcons.js b/server/publications/userGreyIcons.js deleted file mode 100644 index c4d7e359b..000000000 --- a/server/publications/userGreyIcons.js +++ /dev/null @@ -1,7 +0,0 @@ -// Publish only the current logged-in user's GreyIcons profile flag -import { Meteor } from 'meteor/meteor'; - -Meteor.publish('userGreyIcons', function publishUserGreyIcons() { - if (!this.userId) return this.ready(); - return Meteor.users.find({ _id: this.userId }, { fields: { 'profile.GreyIcons': 1 } }); -}); diff --git a/server/publications/users.js b/server/publications/users.js index f929c5db1..4730a3d35 100644 --- a/server/publications/users.js +++ b/server/publications/users.js @@ -1,9 +1,9 @@ -Meteor.publish('user-miniprofile', async function (usernames) { +Meteor.publish('user-miniprofile', function (usernames) { check(usernames, Array); // eslint-disable-next-line no-console // console.log('usernames:', usernames); - const ret = await ReactiveCache.getUsers( + const ret = ReactiveCache.getUsers( { $or: [ { username: { $in: usernames } }, @@ -33,9 +33,9 @@ Meteor.publish('user-admin', function () { return ret; }); -Meteor.publish('user-authenticationMethod', async function (match) { +Meteor.publish('user-authenticationMethod', function (match) { check(match, String); - const ret = await ReactiveCache.getUsers( + const ret = ReactiveCache.getUsers( { $or: [{ _id: match }, { email: match }, { username: match }] }, { fields: { @@ -50,7 +50,7 @@ Meteor.publish('user-authenticationMethod', async function (match) { }); // Secure user search publication for board sharing -Meteor.publish('user-search', async function (searchTerm) { +Meteor.publish('user-search', function (searchTerm) { check(searchTerm, String); // Only allow logged-in users to search for other users @@ -62,7 +62,7 @@ Meteor.publish('user-search', async function (searchTerm) { const searchRegex = new RegExp(searchTerm, 'i'); // Search for users by username, fullname, or email - const ret = await ReactiveCache.getUsers( + const ret = ReactiveCache.getUsers( { $or: [ { username: searchRegex }, diff --git a/server/routes/attachmentApi.js b/server/routes/attachmentApi.js index 309878e20..d08196d19 100644 --- a/server/routes/attachmentApi.js +++ b/server/routes/attachmentApi.js @@ -3,7 +3,6 @@ import { Accounts } from 'meteor/accounts-base'; import { WebApp } from 'meteor/webapp'; import { ReactiveCache } from '/imports/reactiveCache'; import { Attachments, fileStoreStrategyFactory } from '/models/attachments'; -import { Settings } from '../../models/settings'; import { moveToStorage } from '/models/lib/fileStoreStrategy'; import { STORAGE_NAME_FILESYSTEM, STORAGE_NAME_GRIDFS, STORAGE_NAME_S3 } from '/models/lib/fileStoreStrategy'; import AttachmentStorageSettings from '/models/attachmentStorageSettings'; @@ -48,7 +47,7 @@ if (Meteor.isServer) { } // Upload attachment endpoint - WebApp.connectHandlers.use('/api/attachment/upload', async (req, res, next) => { + WebApp.connectHandlers.use('/api/attachment/upload', (req, res, next) => { if (req.method !== 'POST') { return next(); } @@ -62,10 +61,10 @@ if (Meteor.isServer) { try { const userId = authenticateApiRequest(req); - + let body = ''; let bodyComplete = false; - + req.on('data', chunk => { body += chunk.toString(); // Prevent excessive payload @@ -75,11 +74,11 @@ if (Meteor.isServer) { } }); - req.on('end', async () => { + req.on('end', () => { if (bodyComplete) return; // Already processed bodyComplete = true; clearTimeout(timeout); - + try { const data = JSON.parse(body); const { boardId, swimlaneId, listId, cardId, fileData, fileName, fileType, storageBackend } = data; @@ -90,30 +89,16 @@ if (Meteor.isServer) { } // Check if user has permission to modify the card - const card = await ReactiveCache.getCard(cardId); + const card = ReactiveCache.getCard(cardId); if (!card) { return sendErrorResponse(res, 404, 'Card not found'); } - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (!board) { return sendErrorResponse(res, 404, 'Board not found'); } - // Verify that the card belongs to the specified board - if (card.boardId !== boardId) { - return sendErrorResponse(res, 400, 'Card does not belong to the specified board'); - } - - // Verify that the swimlaneId and listId match the card's actual swimlane and list - if (card.swimlaneId !== swimlaneId) { - return sendErrorResponse(res, 400, 'Swimlane ID does not match the card\'s swimlane'); - } - - if (card.listId !== listId) { - return sendErrorResponse(res, 400, 'List ID does not match the card\'s list'); - } - // Check permissions if (!board.isBoardMember(userId)) { return sendErrorResponse(res, 403, 'You do not have permission to modify this card'); @@ -192,7 +177,7 @@ if (Meteor.isServer) { sendErrorResponse(res, 500, error.message); } }); - + req.on('error', (error) => { clearTimeout(timeout); if (!res.headersSent) { @@ -207,7 +192,7 @@ if (Meteor.isServer) { }); // Download attachment endpoint - WebApp.connectHandlers.use('/api/attachment/download/([^/]+)', async (req, res, next) => { + WebApp.connectHandlers.use('/api/attachment/download/([^/]+)', (req, res, next) => { if (req.method !== 'GET') { return next(); } @@ -217,13 +202,13 @@ if (Meteor.isServer) { const attachmentId = req.params[0]; // Get attachment - const attachment = await ReactiveCache.getAttachment(attachmentId); + const attachment = ReactiveCache.getAttachment(attachmentId); if (!attachment) { return sendErrorResponse(res, 404, 'Attachment not found'); } // Check permissions - const board = await ReactiveCache.getBoard(attachment.meta.boardId); + const board = ReactiveCache.getBoard(attachment.meta.boardId); if (!board || !board.isBoardMember(userId)) { return sendErrorResponse(res, 403, 'You do not have permission to access this attachment'); } @@ -245,7 +230,7 @@ if (Meteor.isServer) { readStream.on('end', () => { const fileBuffer = Buffer.concat(chunks); const base64Data = fileBuffer.toString('base64'); - + sendJsonResponse(res, 200, { success: true, attachmentId: attachmentId, @@ -267,7 +252,7 @@ if (Meteor.isServer) { }); // List attachments endpoint - WebApp.connectHandlers.use('/api/attachment/list/([^/]+)/([^/]+)/([^/]+)/([^/]+)', async (req, res, next) => { + WebApp.connectHandlers.use('/api/attachment/list/([^/]+)/([^/]+)/([^/]+)/([^/]+)', (req, res, next) => { if (req.method !== 'GET') { return next(); } @@ -280,19 +265,11 @@ if (Meteor.isServer) { const cardId = req.params[3]; // Check permissions - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (!board || !board.isBoardMember(userId)) { return sendErrorResponse(res, 403, 'You do not have permission to access this board'); } - // If cardId is provided, verify it belongs to the board - if (cardId && cardId !== 'null') { - const card = await ReactiveCache.getCard(cardId); - if (!card || card.boardId !== boardId) { - return sendErrorResponse(res, 404, 'Card not found or does not belong to the specified board'); - } - } - let query = { 'meta.boardId': boardId }; if (swimlaneId && swimlaneId !== 'null') { @@ -307,7 +284,7 @@ if (Meteor.isServer) { query['meta.cardId'] = cardId; } - const attachments = await ReactiveCache.getAttachments(query); + const attachments = ReactiveCache.getAttachments(query); const attachmentList = attachments.map(attachment => { const strategy = fileStoreStrategyFactory.getFileStrategy(attachment, 'original'); @@ -337,7 +314,7 @@ if (Meteor.isServer) { }); // Copy attachment endpoint - WebApp.connectHandlers.use('/api/attachment/copy', async (req, res, next) => { + WebApp.connectHandlers.use('/api/attachment/copy', (req, res, next) => { if (req.method !== 'POST') { return next(); } @@ -350,10 +327,10 @@ if (Meteor.isServer) { try { const userId = authenticateApiRequest(req); - + let body = ''; let bodyComplete = false; - + req.on('data', chunk => { body += chunk.toString(); if (body.length > 10 * 1024 * 1024) { // 10MB limit for metadata @@ -362,52 +339,33 @@ if (Meteor.isServer) { } }); - req.on('end', async () => { + req.on('end', () => { if (bodyComplete) return; bodyComplete = true; clearTimeout(timeout); - + try { const data = JSON.parse(body); const { attachmentId, targetBoardId, targetSwimlaneId, targetListId, targetCardId } = data; // Get source attachment - const sourceAttachment = await ReactiveCache.getAttachment(attachmentId); + const sourceAttachment = ReactiveCache.getAttachment(attachmentId); if (!sourceAttachment) { return sendErrorResponse(res, 404, 'Source attachment not found'); } // Check source permissions - const sourceBoard = await ReactiveCache.getBoard(sourceAttachment.meta.boardId); + const sourceBoard = ReactiveCache.getBoard(sourceAttachment.meta.boardId); if (!sourceBoard || !sourceBoard.isBoardMember(userId)) { return sendErrorResponse(res, 403, 'You do not have permission to access the source attachment'); } // Check target permissions - const targetBoard = await ReactiveCache.getBoard(targetBoardId); + const targetBoard = ReactiveCache.getBoard(targetBoardId); if (!targetBoard || !targetBoard.isBoardMember(userId)) { return sendErrorResponse(res, 403, 'You do not have permission to modify the target card'); } - // Verify that the target card belongs to the target board - const targetCard = await ReactiveCache.getCard(targetCardId); - if (!targetCard) { - return sendErrorResponse(res, 404, 'Target card not found'); - } - - if (targetCard.boardId !== targetBoardId) { - return sendErrorResponse(res, 400, 'Target card does not belong to the specified board'); - } - - // Verify that the target swimlaneId and listId match the card's actual swimlane and list - if (targetCard.swimlaneId !== targetSwimlaneId) { - return sendErrorResponse(res, 400, 'Target swimlane ID does not match the card\'s swimlane'); - } - - if (targetCard.listId !== targetListId) { - return sendErrorResponse(res, 400, 'Target list ID does not match the card\'s list'); - } - // Check if target board allows attachments if (!targetBoard.allowsAttachments) { return sendErrorResponse(res, 403, 'Attachments are not allowed on the target board'); @@ -478,7 +436,7 @@ if (Meteor.isServer) { sendErrorResponse(res, 500, error.message); } }); - + req.on('error', (error) => { clearTimeout(timeout); if (!res.headersSent) { @@ -493,7 +451,7 @@ if (Meteor.isServer) { }); // Move attachment endpoint - WebApp.connectHandlers.use('/api/attachment/move', async (req, res, next) => { + WebApp.connectHandlers.use('/api/attachment/move', (req, res, next) => { if (req.method !== 'POST') { return next(); } @@ -506,10 +464,10 @@ if (Meteor.isServer) { try { const userId = authenticateApiRequest(req); - + let body = ''; let bodyComplete = false; - + req.on('data', chunk => { body += chunk.toString(); if (body.length > 10 * 1024 * 1024) { @@ -518,52 +476,33 @@ if (Meteor.isServer) { } }); - req.on('end', async () => { + req.on('end', () => { if (bodyComplete) return; bodyComplete = true; clearTimeout(timeout); - + try { const data = JSON.parse(body); const { attachmentId, targetBoardId, targetSwimlaneId, targetListId, targetCardId } = data; // Get source attachment - const sourceAttachment = await ReactiveCache.getAttachment(attachmentId); + const sourceAttachment = ReactiveCache.getAttachment(attachmentId); if (!sourceAttachment) { return sendErrorResponse(res, 404, 'Source attachment not found'); } // Check source permissions - const sourceBoard = await ReactiveCache.getBoard(sourceAttachment.meta.boardId); + const sourceBoard = ReactiveCache.getBoard(sourceAttachment.meta.boardId); if (!sourceBoard || !sourceBoard.isBoardMember(userId)) { return sendErrorResponse(res, 403, 'You do not have permission to access the source attachment'); } // Check target permissions - const targetBoard = await ReactiveCache.getBoard(targetBoardId); + const targetBoard = ReactiveCache.getBoard(targetBoardId); if (!targetBoard || !targetBoard.isBoardMember(userId)) { return sendErrorResponse(res, 403, 'You do not have permission to modify the target card'); } - // Verify that the target card belongs to the target board - const targetCard = await ReactiveCache.getCard(targetCardId); - if (!targetCard) { - return sendErrorResponse(res, 404, 'Target card not found'); - } - - if (targetCard.boardId !== targetBoardId) { - return sendErrorResponse(res, 400, 'Target card does not belong to the specified board'); - } - - // Verify that the target swimlaneId and listId match the card's actual swimlane and list - if (targetCard.swimlaneId !== targetSwimlaneId) { - return sendErrorResponse(res, 400, 'Target swimlane ID does not match the card\'s swimlane'); - } - - if (targetCard.listId !== targetListId) { - return sendErrorResponse(res, 400, 'Target list ID does not match the card\'s list'); - } - // Check if target board allows attachments if (!targetBoard.allowsAttachments) { return sendErrorResponse(res, 403, 'Attachments are not allowed on the target board'); @@ -595,7 +534,7 @@ if (Meteor.isServer) { sendErrorResponse(res, 500, error.message); } }); - + req.on('error', (error) => { clearTimeout(timeout); if (!res.headersSent) { @@ -610,7 +549,7 @@ if (Meteor.isServer) { }); // Delete attachment endpoint - WebApp.connectHandlers.use('/api/attachment/delete/([^/]+)', async (req, res, next) => { + WebApp.connectHandlers.use('/api/attachment/delete/([^/]+)', (req, res, next) => { if (req.method !== 'DELETE') { return next(); } @@ -620,13 +559,13 @@ if (Meteor.isServer) { const attachmentId = req.params[0]; // Get attachment - const attachment = await ReactiveCache.getAttachment(attachmentId); + const attachment = ReactiveCache.getAttachment(attachmentId); if (!attachment) { return sendErrorResponse(res, 404, 'Attachment not found'); } // Check permissions - const board = await ReactiveCache.getBoard(attachment.meta.boardId); + const board = ReactiveCache.getBoard(attachment.meta.boardId); if (!board || !board.isBoardMember(userId)) { return sendErrorResponse(res, 403, 'You do not have permission to delete this attachment'); } @@ -646,7 +585,7 @@ if (Meteor.isServer) { }); // Get attachment info endpoint - WebApp.connectHandlers.use('/api/attachment/info/([^/]+)', async (req, res, next) => { + WebApp.connectHandlers.use('/api/attachment/info/([^/]+)', (req, res, next) => { if (req.method !== 'GET') { return next(); } @@ -656,19 +595,19 @@ if (Meteor.isServer) { const attachmentId = req.params[0]; // Get attachment - const attachment = await ReactiveCache.getAttachment(attachmentId); + const attachment = ReactiveCache.getAttachment(attachmentId); if (!attachment) { return sendErrorResponse(res, 404, 'Attachment not found'); } // Check permissions - const board = await ReactiveCache.getBoard(attachment.meta.boardId); + const board = ReactiveCache.getBoard(attachment.meta.boardId); if (!board || !board.isBoardMember(userId)) { return sendErrorResponse(res, 403, 'You do not have permission to access this attachment'); } const strategy = fileStoreStrategyFactory.getFileStrategy(attachment, 'original'); - + sendJsonResponse(res, 200, { success: true, attachmentId: attachment._id, diff --git a/server/routes/avatarServer.js b/server/routes/avatarServer.js index e5c29baee..008ea573a 100644 --- a/server/routes/avatarServer.js +++ b/server/routes/avatarServer.js @@ -13,14 +13,14 @@ import path from 'path'; if (Meteor.isServer) { // Handle avatar file downloads - WebApp.connectHandlers.use('/cdn/storage/avatars/([^/]+)', async (req, res, next) => { + WebApp.connectHandlers.use('/cdn/storage/avatars/([^/]+)', (req, res, next) => { if (req.method !== 'GET') { return next(); } try { const fileName = req.params[0]; - + if (!fileName) { res.writeHead(400); res.end('Invalid avatar file name'); @@ -29,7 +29,7 @@ if (Meteor.isServer) { // Extract file ID from filename (format: fileId-original-filename) const fileId = fileName.split('-original-')[0]; - + if (!fileId) { res.writeHead(400); res.end('Invalid avatar file format'); @@ -37,7 +37,7 @@ if (Meteor.isServer) { } // Get avatar file from database - const avatar = await ReactiveCache.getAvatar(fileId); + const avatar = ReactiveCache.getAvatar(fileId); if (!avatar) { res.writeHead(404); res.end('Avatar not found'); @@ -68,7 +68,7 @@ if (Meteor.isServer) { res.setHeader('Content-Length', avatar.size || 0); res.setHeader('Cache-Control', 'public, max-age=31536000'); // Cache for 1 year res.setHeader('ETag', `"${avatar._id}"`); - + // Handle conditional requests const ifNoneMatch = req.headers['if-none-match']; if (ifNoneMatch && ifNoneMatch === `"${avatar._id}"`) { @@ -106,12 +106,12 @@ if (Meteor.isServer) { try { const fileName = req.params[0]; - + // Redirect to new avatar URL format const newUrl = `/cdn/storage/avatars/${fileName}`; res.writeHead(301, { 'Location': newUrl }); res.end(); - + } catch (error) { console.error('Legacy avatar redirect error:', error); res.writeHead(500); diff --git a/server/routes/customHeadAssets.js b/server/routes/customHeadAssets.js deleted file mode 100644 index f4a5b1bce..000000000 --- a/server/routes/customHeadAssets.js +++ /dev/null @@ -1,81 +0,0 @@ -import { WebApp } from 'meteor/webapp'; -import { Meteor } from 'meteor/meteor'; -import fs from 'fs'; -import path from 'path'; -import Settings from '/models/settings'; - -const shouldServeContent = (value) => - typeof value === 'string' && value.trim().length > 0; - -const getDefaultFileContent = (filename) => { - try { - const filePath = path.join(Meteor.absolutePath, 'public', filename); - if (fs.existsSync(filePath)) { - return fs.readFileSync(filePath, 'utf-8'); - } - } catch (e) { - console.error(`Error reading default file ${filename}:`, e); - } - return null; -}; - -const respondWithText = (res, contentType, body) => { - res.writeHead(200, { - 'Content-Type': `${contentType}; charset=utf-8`, - 'Access-Control-Allow-Origin': '*', - }); - res.end(body); -}; - -WebApp.connectHandlers.use('/site.webmanifest', (req, res, next) => { - if (req.method !== 'GET' && req.method !== 'HEAD') return next(); - const setting = Settings.findOne( - {}, - { - fields: { - customHeadEnabled: 1, - customManifestEnabled: 1, - customManifestContent: 1, - }, - }, - ); - - // Serve custom content if enabled - if (setting && setting.customHeadEnabled && setting.customManifestEnabled && shouldServeContent(setting.customManifestContent)) { - return respondWithText(res, 'application/manifest+json', setting.customManifestContent); - } - - // Fallback to default manifest file - const defaultContent = getDefaultFileContent('site.webmanifest.default'); - if (defaultContent) { - return respondWithText(res, 'application/manifest+json', defaultContent); - } - - return next(); -}); - -WebApp.connectHandlers.use('/.well-known/assetlinks.json', (req, res, next) => { - if (req.method !== 'GET' && req.method !== 'HEAD') return next(); - const setting = Settings.findOne( - {}, - { - fields: { - customAssetLinksEnabled: 1, - customAssetLinksContent: 1, - }, - }, - ); - - // Serve custom content if enabled - if (setting && setting.customAssetLinksEnabled && shouldServeContent(setting.customAssetLinksContent)) { - return respondWithText(res, 'application/json', setting.customAssetLinksContent); - } - - // Fallback to default assetlinks file - const defaultContent = getDefaultFileContent('.well-known/assetlinks.json.default'); - if (defaultContent) { - return respondWithText(res, 'application/json', defaultContent); - } - - return next(); -}); diff --git a/server/routes/legacyAttachments.js b/server/routes/legacyAttachments.js index 9b8d9e502..a9660efc6 100644 --- a/server/routes/legacyAttachments.js +++ b/server/routes/legacyAttachments.js @@ -8,49 +8,6 @@ if (process.env.DEBUG === 'true') { console.log('Legacy attachments route loaded'); } -/** - * Helper function to properly encode a filename for the Content-Disposition header - * Removes invalid characters (control chars, newlines, etc.) that would break HTTP headers. - * For non-ASCII filenames, uses RFC 5987 encoding to preserve the original filename. - * This prevents ERR_INVALID_CHAR errors when filenames contain control characters. - */ -function sanitizeFilenameForHeader(filename) { - if (!filename || typeof filename !== 'string') { - return 'download'; - } - - // First, remove any control characters (0x00-0x1F, 0x7F) that would break HTTP headers - // This includes newlines, carriage returns, tabs, and other control chars - let sanitized = filename.replace(/[\x00-\x1F\x7F]/g, ''); - - // If the filename is all ASCII printable characters (0x20-0x7E), use it directly - if (/^[\x20-\x7E]*$/.test(sanitized)) { - // Escape any quotes and backslashes in the filename - sanitized = sanitized.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); - return sanitized; - } - - // For non-ASCII filenames, provide a fallback and RFC 5987 encoded version - const fallback = sanitized.replace(/[^\x20-\x7E]/g, '_').slice(0, 100) || 'download'; - const encoded = encodeURIComponent(sanitized); - - // Return special marker format that will be handled by buildContentDispositionHeader - // Format: "fallback|RFC5987:encoded" - return `${fallback}|RFC5987:${encoded}`; -} - -/** - * Helper function to build a complete Content-Disposition header value with RFC 5987 support - * Handles the special format returned by sanitizeFilenameForHeader for non-ASCII filenames - */ -function buildContentDispositionHeader(disposition, sanitizedFilename) { - if (sanitizedFilename.includes('|RFC5987:')) { - const [fallback, encoded] = sanitizedFilename.split('|RFC5987:'); - return `${disposition}; filename="${fallback}"; filename*=UTF-8''${encoded}`; - } - return `${disposition}; filename="${sanitizedFilename}"`; -} - /** * Legacy attachment download route for CollectionFS compatibility * Handles downloads from old CollectionFS structure @@ -58,7 +15,7 @@ function buildContentDispositionHeader(disposition, sanitizedFilename) { if (Meteor.isServer) { // Handle legacy attachment downloads - WebApp.connectHandlers.use('/cfs/files/attachments', async (req, res, next) => { + WebApp.connectHandlers.use('/cfs/files/attachments', (req, res, next) => { const attachmentId = req.url.split('/').pop(); if (!attachmentId) { @@ -78,7 +35,7 @@ if (Meteor.isServer) { } // Check permissions - const board = await ReactiveCache.getBoard(attachment.meta.boardId); + const board = ReactiveCache.getBoard(attachment.meta.boardId); if (!board) { res.writeHead(404); res.end('Board not found'); @@ -100,7 +57,7 @@ if (Meteor.isServer) { // Force attachment disposition for SVG files to prevent XSS attacks const isSvgFile = attachment.name && attachment.name.toLowerCase().endsWith('.svg'); const disposition = isSvgFile ? 'attachment' : 'attachment'; // Always use attachment for legacy files - res.setHeader('Content-Disposition', buildContentDispositionHeader(disposition, sanitizeFilenameForHeader(attachment.name))); + res.setHeader('Content-Disposition', `${disposition}; filename="${attachment.name}"`); // Add security headers for SVG files if (isSvgFile) { diff --git a/server/routes/universalFileServer.js b/server/routes/universalFileServer.js index 0036b75d4..3e7159078 100644 --- a/server/routes/universalFileServer.js +++ b/server/routes/universalFileServer.js @@ -26,7 +26,7 @@ if (Meteor.isServer) { const nameLower = (fileObj.name || '').toLowerCase(); const typeLower = (fileObj.type || '').toLowerCase(); const isPdfByExt = nameLower.endsWith('.pdf'); - + // Define dangerous types that must never be served inline const dangerousTypes = new Set([ 'text/html', @@ -37,7 +37,7 @@ if (Meteor.isServer) { 'application/javascript', 'text/javascript' ]); - + // Define safe types that can be served inline for viewing const safeInlineTypes = new Set([ 'application/pdf', @@ -59,7 +59,7 @@ if (Meteor.isServer) { 'text/plain', 'application/json' ]); - + const isSvg = nameLower.endsWith('.svg') || typeLower === 'image/svg+xml'; const isDangerous = dangerousTypes.has(typeLower) || isSvg; // Consider PDF safe inline by extension if type is missing/mis-set @@ -80,7 +80,7 @@ if (Meteor.isServer) { if (isDangerous) { // SECURITY: Force download for dangerous types to prevent XSS res.setHeader('Content-Type', 'application/octet-stream'); - res.setHeader('Content-Disposition', buildContentDispositionHeader('attachment', sanitizeFilenameForHeader(fileObj.name))); + res.setHeader('Content-Disposition', `attachment; filename="${fileObj.name}"`); res.setHeader('Content-Security-Policy', "default-src 'none'; sandbox;"); res.setHeader('X-Frame-Options', 'DENY'); } else if (isSafeInline) { @@ -88,13 +88,13 @@ if (Meteor.isServer) { // If the file is a PDF by extension but type is wrong/missing, correct it const finalType = (isPdfByExt && typeLower !== 'application/pdf') ? 'application/pdf' : (typeLower || 'application/octet-stream'); res.setHeader('Content-Type', finalType); - res.setHeader('Content-Disposition', buildContentDispositionHeader('inline', sanitizeFilenameForHeader(fileObj.name))); + res.setHeader('Content-Disposition', `inline; filename="${fileObj.name}"`); // Restrictive CSP for safe types - allow media/img/object for viewer embeds, no scripts res.setHeader('Content-Security-Policy', "default-src 'none'; object-src 'self'; media-src 'self'; img-src 'self'; style-src 'unsafe-inline';"); } else { // Unknown types: force download as fallback res.setHeader('Content-Type', 'application/octet-stream'); - res.setHeader('Content-Disposition', buildContentDispositionHeader('attachment', sanitizeFilenameForHeader(fileObj.name))); + res.setHeader('Content-Disposition', `attachment; filename="${fileObj.name}"`); res.setHeader('Content-Security-Policy', "default-src 'none'; sandbox;"); } } else { @@ -102,13 +102,13 @@ if (Meteor.isServer) { if (isSvg || isDangerous) { // Serve potentially dangerous avatar types as downloads instead res.setHeader('Content-Type', 'application/octet-stream'); - res.setHeader('Content-Disposition', buildContentDispositionHeader('attachment', sanitizeFilenameForHeader(fileObj.name))); + res.setHeader('Content-Disposition', `attachment; filename="${fileObj.name}"`); res.setHeader('Content-Security-Policy', "default-src 'none'; sandbox;"); res.setHeader('X-Frame-Options', 'DENY'); } else { // For typical image avatars, use provided type if present, otherwise fall back to a safe generic image type res.setHeader('Content-Type', typeLower || 'image/jpeg'); - res.setHeader('Content-Disposition', buildContentDispositionHeader('inline', sanitizeFilenameForHeader(fileObj.name))); + res.setHeader('Content-Disposition', `inline; filename="${fileObj.name}"`); } } } @@ -172,7 +172,7 @@ if (Meteor.isServer) { * - Else if avatar's owner belongs to at least one public board -> allow * - Otherwise -> deny */ - async function isAuthorizedForAvatar(req, avatar) { + function isAuthorizedForAvatar(req, avatar) { try { if (!avatar) return false; @@ -180,7 +180,7 @@ if (Meteor.isServer) { const q = parseQuery(req); const boardId = q.boardId || q.board || q.b; if (boardId) { - const board = await ReactiveCache.getBoard(boardId); + const board = ReactiveCache.getBoard(boardId); if (board && board.isPublic && board.isPublic()) return true; // If private board is specified, require membership of requester @@ -297,7 +297,7 @@ if (Meteor.isServer) { * - Public boards: allow * - Private boards: require valid user who is a member */ - async function isAuthorizedForBoard(req, board) { + function isAuthorizedForBoard(req, board) { try { if (!board) return false; if (board.isPublic && board.isPublic()) return true; @@ -312,54 +312,6 @@ if (Meteor.isServer) { } } - /** - * Helper function to properly encode a filename for the Content-Disposition header. - * Removes invalid characters (control chars, newlines, etc.) that would break HTTP headers. - * For non-ASCII filenames, uses RFC 5987 encoding to preserve the original filename. - * This prevents ERR_INVALID_CHAR errors when filenames contain control characters. - * - * Example: - * - ASCII filename: sanitizeFilenameForHeader('test.txt') => 'test.txt' - * - Non-ASCII: sanitizeFilenameForHeader('現有檔案.odt') => 'file.odt'; filename*=UTF-8''%E7%8F%BE%E6%9C%89%E6%AA%94%E6%A1%88.odt - * - Control chars: sanitizeFilenameForHeader('test\nfile.txt') => 'testfile.txt' - */ - function sanitizeFilenameForHeader(filename) { - if (!filename || typeof filename !== 'string') { - return 'download'; - } - - // First, remove any control characters (0x00-0x1F, 0x7F) that would break HTTP headers - // This includes newlines, carriage returns, tabs, and other control chars - let sanitized = filename.replace(/[\x00-\x1F\x7F]/g, ''); - - // If the filename is all ASCII printable characters (0x20-0x7E), use it directly - if (/^[\x20-\x7E]*$/.test(sanitized)) { - // Escape any quotes and backslashes in the filename - sanitized = sanitized.replace(/\\/g, '\\\\').replace(/"/g, '\\"'); - return sanitized; - } - - // For non-ASCII filenames, provide a fallback and RFC 5987 encoded version - const fallback = sanitized.replace(/[^\x20-\x7E]/g, '_').slice(0, 100) || 'download'; - const encoded = encodeURIComponent(sanitized); - - // Return special marker format that will be handled by buildContentDispositionHeader - // Format: "fallback|RFC5987:encoded" - return `${fallback}|RFC5987:${encoded}`; - } - - /** - * Helper function to build a complete Content-Disposition header value with RFC 5987 support - * Handles the special format returned by sanitizeFilenameForHeader for non-ASCII filenames - */ - function buildContentDispositionHeader(disposition, sanitizedFilename) { - if (sanitizedFilename.includes('|RFC5987:')) { - const [fallback, encoded] = sanitizedFilename.split('|RFC5987:'); - return `${disposition}; filename="${fallback}"; filename*=UTF-8''${encoded}`; - } - return `${disposition}; filename="${sanitizedFilename}"`; - } - /** * Helper function to stream file with error handling */ @@ -389,14 +341,14 @@ if (Meteor.isServer) { * Serve attachments from new Meteor-Files structure * Route: /cdn/storage/attachments/{fileId} or /cdn/storage/attachments/{fileId}/original/{filename} */ - WebApp.connectHandlers.use('/cdn/storage/attachments', async (req, res, next) => { + WebApp.connectHandlers.use('/cdn/storage/attachments', (req, res, next) => { if (req.method !== 'GET') { return next(); } try { const fileId = extractFirstIdFromUrl(req, '/cdn/storage/attachments'); - + if (!fileId) { res.writeHead(400); res.end('Invalid attachment file ID'); @@ -412,7 +364,7 @@ if (Meteor.isServer) { } // Check permissions - const board = await ReactiveCache.getBoard(attachment.meta.boardId); + const board = ReactiveCache.getBoard(attachment.meta.boardId); if (!board) { res.writeHead(404); res.end('Board not found'); @@ -420,7 +372,7 @@ if (Meteor.isServer) { } // Enforce cookie/header/query-based auth for private boards - if (!(await isAuthorizedForBoard(req, board))) { + if (!isAuthorizedForBoard(req, board)) { res.writeHead(403); res.end('Access denied'); return; @@ -456,7 +408,7 @@ if (Meteor.isServer) { if (attachment.size) res.setHeader('Content-Length', attachment.size); res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('Content-Type', 'application/octet-stream'); - res.setHeader('Content-Disposition', buildContentDispositionHeader('attachment', sanitizeFilenameForHeader(attachment.name))); + res.setHeader('Content-Disposition', `attachment; filename="${attachment.name}"`); res.setHeader('Content-Security-Policy', "default-src 'none'; sandbox;"); } else { setFileHeaders(res, attachment, true); @@ -476,14 +428,14 @@ if (Meteor.isServer) { * Serve avatars from new Meteor-Files structure * Route: /cdn/storage/avatars/{fileId} or /cdn/storage/avatars/{fileId}/original/{filename} */ - WebApp.connectHandlers.use('/cdn/storage/avatars', async (req, res, next) => { + WebApp.connectHandlers.use('/cdn/storage/avatars', (req, res, next) => { if (req.method !== 'GET') { return next(); } try { const fileId = extractFirstIdFromUrl(req, '/cdn/storage/avatars'); - + if (!fileId) { res.writeHead(400); res.end('Invalid avatar file ID'); @@ -491,7 +443,7 @@ if (Meteor.isServer) { } // Get avatar from database - const avatar = await ReactiveCache.getAvatar(fileId); + const avatar = ReactiveCache.getAvatar(fileId); if (!avatar) { res.writeHead(404); res.end('Avatar not found'); @@ -499,7 +451,7 @@ if (Meteor.isServer) { } // Enforce visibility: avatars are public only in the context of public boards - if (!(await isAuthorizedForAvatar(req, avatar))) { + if (!isAuthorizedForAvatar(req, avatar)) { res.writeHead(403); res.end('Access denied'); return; @@ -541,14 +493,14 @@ if (Meteor.isServer) { * Serve legacy attachments from CollectionFS structure * Route: /cfs/files/attachments/{attachmentId} */ - WebApp.connectHandlers.use('/cfs/files/attachments', async (req, res, next) => { + WebApp.connectHandlers.use('/cfs/files/attachments', (req, res, next) => { if (req.method !== 'GET') { return next(); } try { const attachmentId = extractFirstIdFromUrl(req, '/cfs/files/attachments'); - + if (!attachmentId) { res.writeHead(400); res.end('Invalid attachment ID'); @@ -564,7 +516,7 @@ if (Meteor.isServer) { } // Check permissions - const board = await ReactiveCache.getBoard(attachment.meta.boardId); + const board = ReactiveCache.getBoard(attachment.meta.boardId); if (!board) { res.writeHead(404); res.end('Board not found'); @@ -572,7 +524,7 @@ if (Meteor.isServer) { } // Enforce cookie/header/query-based auth for private boards - if (!(await isAuthorizedForBoard(req, board))) { + if (!isAuthorizedForBoard(req, board)) { res.writeHead(403); res.end('Access denied'); return; @@ -593,7 +545,7 @@ if (Meteor.isServer) { if (attachment.size) res.setHeader('Content-Length', attachment.size); res.setHeader('X-Content-Type-Options', 'nosniff'); res.setHeader('Content-Type', 'application/octet-stream'); - res.setHeader('Content-Disposition', buildContentDispositionHeader('attachment', sanitizeFilenameForHeader(attachment.name))); + res.setHeader('Content-Disposition', `attachment; filename="${attachment.name}"`); res.setHeader('Content-Security-Policy', "default-src 'none'; sandbox;"); } else { setFileHeaders(res, attachment, true); @@ -617,14 +569,14 @@ if (Meteor.isServer) { * Serve legacy avatars from CollectionFS structure * Route: /cfs/files/avatars/{avatarId} */ - WebApp.connectHandlers.use('/cfs/files/avatars', async (req, res, next) => { + WebApp.connectHandlers.use('/cfs/files/avatars', (req, res, next) => { if (req.method !== 'GET') { return next(); } try { const avatarId = extractFirstIdFromUrl(req, '/cfs/files/avatars'); - + if (!avatarId) { res.writeHead(400); res.end('Invalid avatar ID'); @@ -632,8 +584,8 @@ if (Meteor.isServer) { } // Try to get avatar from database (new structure first) - let avatar = await ReactiveCache.getAvatar(avatarId); - + let avatar = ReactiveCache.getAvatar(avatarId); + // If not found in new structure, try to handle legacy format if (!avatar) { // For legacy avatars, we might need to handle different ID formats @@ -644,7 +596,7 @@ if (Meteor.isServer) { } // Enforce visibility for legacy avatars as well - if (!(await isAuthorizedForAvatar(req, avatar))) { + if (!isAuthorizedForAvatar(req, avatar)) { res.writeHead(403); res.end('Access denied'); return; diff --git a/server/rulesHelper.js b/server/rulesHelper.js index 0df49dff1..d5efe1d3f 100644 --- a/server/rulesHelper.js +++ b/server/rulesHelper.js @@ -1,48 +1,48 @@ import { ReactiveCache } from '/imports/reactiveCache'; RulesHelper = { - async executeRules(activity) { - const matchingRules = await this.findMatchingRules(activity); + executeRules(activity) { + const matchingRules = this.findMatchingRules(activity); for (let i = 0; i < matchingRules.length; i++) { - const action = await matchingRules[i].getAction(); + const action = matchingRules[i].getAction(); if (action !== undefined) { - await this.performAction(activity, action); + this.performAction(activity, action); } } }, - async findMatchingRules(activity) { + findMatchingRules(activity) { const activityType = activity.activityType; if (TriggersDef[activityType] === undefined) { return []; } const matchingFields = TriggersDef[activityType].matchingFields; - const matchingMap = await this.buildMatchingFieldsMap(activity, matchingFields); - const matchingTriggers = await ReactiveCache.getTriggers(matchingMap); + const matchingMap = this.buildMatchingFieldsMap(activity, matchingFields); + const matchingTriggers = ReactiveCache.getTriggers(matchingMap); const matchingRules = []; - for (const trigger of matchingTriggers) { - const rule = await trigger.getRule(); + matchingTriggers.forEach(function(trigger) { + const rule = trigger.getRule(); // Check that for some unknown reason there are some leftover triggers // not connected to any rules if (rule !== undefined) { - matchingRules.push(rule); + matchingRules.push(trigger.getRule()); } - } + }); return matchingRules; }, - async buildMatchingFieldsMap(activity, matchingFields) { + buildMatchingFieldsMap(activity, matchingFields) { const matchingMap = { activityType: activity.activityType }; - for (const field of matchingFields) { + matchingFields.forEach(field => { // Creating a matching map with the actual field of the activity // and with the wildcard (for example: trigger when a card is added // in any [*] board let value = activity[field]; if (field === 'oldListName') { - const oldList = await ReactiveCache.getList(activity.oldListId); + const oldList = ReactiveCache.getList(activity.oldListId); if (oldList) { value = oldList.title; } } else if (field === 'oldSwimlaneName') { - const oldSwimlane = await ReactiveCache.getSwimlane(activity.oldSwimlaneId); + const oldSwimlane = ReactiveCache.getSwimlane(activity.oldSwimlaneId); if (oldSwimlane) { value = oldSwimlane.title; } @@ -54,11 +54,11 @@ RulesHelper = { matchingMap[field] = { $in: matchesList, }; - } + }); return matchingMap; }, - async performAction(activity, action) { - const card = await ReactiveCache.getCard(activity.cardId); + performAction(activity, action) { + const card = ReactiveCache.getCard(activity.cardId); const boardId = activity.boardId; if ( action.actionType === 'moveCardToTop' || @@ -67,12 +67,12 @@ RulesHelper = { let list; let listId; if (action.listName === '*') { - list = await card.list(); + list = card.list(); if (boardId !== action.boardId) { - list = await ReactiveCache.getList({ title: list.title, boardId: action.boardId }); + list = ReactiveCache.getList({ title: list.title, boardId: action.boardId }); } } else { - list = await ReactiveCache.getList({ + list = ReactiveCache.getList({ title: action.listName, boardId: action.boardId, }); @@ -86,38 +86,38 @@ RulesHelper = { let swimlane; let swimlaneId; if (action.swimlaneName === '*') { - swimlane = await ReactiveCache.getSwimlane(card.swimlaneId); + swimlane = ReactiveCache.getSwimlane(card.swimlaneId); if (boardId !== action.boardId) { - swimlane = await ReactiveCache.getSwimlane({ + swimlane = ReactiveCache.getSwimlane({ title: swimlane.title, boardId: action.boardId, }); } } else { - swimlane = await ReactiveCache.getSwimlane({ + swimlane = ReactiveCache.getSwimlane({ title: action.swimlaneName, boardId: action.boardId, }); } if (swimlane === undefined) { - swimlaneId = (await ReactiveCache.getSwimlane({ + swimlaneId = ReactiveCache.getSwimlane({ title: 'Default', boardId: action.boardId, - }))._id; + })._id; } else { swimlaneId = swimlane._id; } if (action.actionType === 'moveCardToTop') { const minOrder = _.min( - (await list.cardsUnfiltered(swimlaneId)).map(c => c.sort), + list.cardsUnfiltered(swimlaneId).map(c => c.sort), ); - await card.move(action.boardId, swimlaneId, listId, minOrder - 1); + card.move(action.boardId, swimlaneId, listId, minOrder - 1); } else { const maxOrder = _.max( - (await list.cardsUnfiltered(swimlaneId)).map(c => c.sort), + list.cardsUnfiltered(swimlaneId).map(c => c.sort), ); - await card.move(action.boardId, swimlaneId, listId, maxOrder + 1); + card.move(action.boardId, swimlaneId, listId, maxOrder + 1); } } if (action.actionType === 'sendEmail') { @@ -132,7 +132,7 @@ RulesHelper = { // Check if recipient is a Wekan user to get their language if (to && to.includes('@')) { - recipientUser = await ReactiveCache.getUser({ 'emails.address': to.toLowerCase() }); + recipientUser = ReactiveCache.getUser({ 'emails.address': to.toLowerCase() }); if (recipientUser && typeof recipientUser.getLanguage === 'function') { recipientLang = recipientUser.getLanguage(); } @@ -247,13 +247,13 @@ RulesHelper = { } } if (action.actionType === 'archive') { - await card.archive(); + card.archive(); } if (action.actionType === 'unarchive') { - await card.restore(); + card.restore(); } if (action.actionType === 'setColor') { - await card.setColor(action.selectedColor); + card.setColor(action.selectedColor); } if (action.actionType === 'addLabel') { card.addLabel(action.labelId); @@ -262,7 +262,7 @@ RulesHelper = { card.removeLabel(action.labelId); } if (action.actionType === 'addMember') { - const memberId = (await ReactiveCache.getUser({ username: action.username }))._id; + const memberId = ReactiveCache.getUser({ username: action.username })._id; card.assignMember(memberId); } if (action.actionType === 'removeMember') { @@ -272,45 +272,45 @@ RulesHelper = { card.unassignMember(members[i]); } } else { - const memberId = (await ReactiveCache.getUser({ username: action.username }))._id; + const memberId = ReactiveCache.getUser({ username: action.username })._id; card.unassignMember(memberId); } } if (action.actionType === 'checkAll') { - const checkList = await ReactiveCache.getChecklist({ + const checkList = ReactiveCache.getChecklist({ title: action.checklistName, cardId: card._id, }); - await checkList.checkAllItems(); + checkList.checkAllItems(); } if (action.actionType === 'uncheckAll') { - const checkList = await ReactiveCache.getChecklist({ + const checkList = ReactiveCache.getChecklist({ title: action.checklistName, cardId: card._id, }); - await checkList.uncheckAllItems(); + checkList.uncheckAllItems(); } if (action.actionType === 'checkItem') { - const checkList = await ReactiveCache.getChecklist({ + const checkList = ReactiveCache.getChecklist({ title: action.checklistName, cardId: card._id, }); - const checkItem = await ReactiveCache.getChecklistItem({ + const checkItem = ReactiveCache.getChecklistItem({ title: action.checkItemName, checkListId: checkList._id, }); - await checkItem.check(); + checkItem.check(); } if (action.actionType === 'uncheckItem') { - const checkList = await ReactiveCache.getChecklist({ + const checkList = ReactiveCache.getChecklist({ title: action.checklistName, cardId: card._id, }); - const checkItem = await ReactiveCache.getChecklistItem({ + const checkItem = ReactiveCache.getChecklistItem({ title: action.checkItemName, checkListId: checkList._id, }); - await checkItem.uncheck(); + checkItem.uncheck(); } if (action.actionType === 'addChecklist') { Checklists.insert({ @@ -340,22 +340,21 @@ RulesHelper = { sort: 0, }); const itemsArray = action.checklistItems.split(','); - const existingItems = await ReactiveCache.getChecklistItems({ checklistId: checkListId }); - const sortBase = existingItems.length; + const checkList = ReactiveCache.getChecklist(checkListId); for (let i = 0; i < itemsArray.length; i++) { ChecklistItems.insert({ title: itemsArray[i], checklistId: checkListId, cardId: card._id, - sort: sortBase + i, + sort: checkList.itemCount(), }); } } if (action.actionType === 'createCard') { - const list = await ReactiveCache.getList({ title: action.listName, boardId }); + const list = ReactiveCache.getList({ title: action.listName, boardId }); let listId = ''; let swimlaneId = ''; - const swimlane = await ReactiveCache.getSwimlane({ + const swimlane = ReactiveCache.getSwimlane({ title: action.swimlaneName, boardId, }); @@ -365,7 +364,7 @@ RulesHelper = { listId = list._id; } if (swimlane === undefined) { - swimlaneId = (await ReactiveCache.getSwimlane({ title: 'Default', boardId }))._id; + swimlaneId = ReactiveCache.getSwimlane({ title: 'Default', boardId })._id; } else { swimlaneId = swimlane._id; } @@ -378,11 +377,11 @@ RulesHelper = { }); } if (action.actionType === 'linkCard') { - const list = await ReactiveCache.getList({ title: action.listName, boardId: action.boardId }); - const card = await ReactiveCache.getCard(activity.cardId); + const list = ReactiveCache.getList({ title: action.listName, boardId: action.boardId }); + const card = ReactiveCache.getCard(activity.cardId); let listId = ''; let swimlaneId = ''; - const swimlane = await ReactiveCache.getSwimlane({ + const swimlane = ReactiveCache.getSwimlane({ title: action.swimlaneName, boardId: action.boardId, }); @@ -392,7 +391,7 @@ RulesHelper = { listId = list._id; } if (swimlane === undefined) { - swimlaneId = (await ReactiveCache.getSwimlane({ title: 'Default', boardId: action.boardId }))._id; + swimlaneId = ReactiveCache.getSwimlane({ title: 'Default', boardId: action.boardId })._id; } else { swimlaneId = swimlane._id; } diff --git a/snapcraft.yaml b/snapcraft.yaml index a6a9008ec..3e3e503bf 100644 --- a/snapcraft.yaml +++ b/snapcraft.yaml @@ -1,5 +1,5 @@ name: wekan -version: '8.35' +version: '8.16' base: core24 summary: Open Source kanban description: | @@ -78,7 +78,7 @@ apps: parts: mongodb: - source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2204-7.0.30.tgz + source: https://fastdl.mongodb.org/linux/mongodb-linux-x86_64-ubuntu2204-7.0.25.tgz plugin: dump stage-packages: - libssl3 @@ -166,9 +166,9 @@ parts: # Cleanup mkdir .build cd .build - wget https://github.com/wekan/wekan/releases/download/v8.35/wekan-8.35-amd64.zip - unzip wekan-8.35-amd64.zip - rm wekan-8.35-amd64.zip + wget https://github.com/wekan/wekan/releases/download/v8.16/wekan-8.16-amd64.zip + unzip wekan-8.16-amd64.zip + rm wekan-8.16-amd64.zip cd .. ##cd .build/bundle ##find . -type d -name '*-garbage*' | xargs rm -rf diff --git a/start-wekan.bat b/start-wekan.bat index cc7b7e055..a3f1a2984 100644 --- a/start-wekan.bat +++ b/start-wekan.bat @@ -14,16 +14,6 @@ REM # MONGO_PASSWORD_FILE : MongoDB password file (Docker secrets) REM # example : SET MONGO_PASSWORD_FILE=/run/secrets/mongo_password REM SET MONGO_PASSWORD_FILE= -REM # MONGO_OPLOG_URL: MongoDB oplog connection (highly recommended for pub/sub performance) -REM # Required for Meteor reactive subscriptions to work efficiently -REM # Must point to a MongoDB replica set (local oplog or remote) -REM # For local MongoDB with replicaSet named 'rs0', use: -REM # SET MONGO_OPLOG_URL=mongodb://127.0.0.1:27017/local?replicaSet=rs0 -REM # For production with credentials and remote MongoDB: -REM # SET MONGO_OPLOG_URL=mongodb://:@:/local?authSource=admin&replicaSet=rsWekan -REM # Without this, Meteor falls back to polling which increases CPU usage and latency -REM SET MONGO_OPLOG_URL=mongodb://127.0.0.1:27017/local?replicaSet=rs0 - REM # If port is 80, must change ROOT_URL to: http://YOUR-WEKAN-SERVER-IPv4-ADDRESS , like http://192.168.0.100 REM # If port is not 80, must change ROOT_URL to: http://YOUR-WEKAN-SERVER-IPv4-ADDRESS:YOUR-PORT-NUMBER , like http://192.168.0.100:2000 REM # If ROOT_URL is not correct, these do not work: translations, uploading attachments. diff --git a/start-wekan.sh b/start-wekan.sh index 42af836ee..8d91b7df4 100755 --- a/start-wekan.sh +++ b/start-wekan.sh @@ -13,16 +13,6 @@ # example : export MONGO_PASSWORD_FILE=/run/secrets/mongo_password #export MONGO_PASSWORD_FILE= #----------------------------------------------------------------- - # MONGO_OPLOG_URL: MongoDB oplog connection (highly recommended for pub/sub performance) - # Required for Meteor reactive subscriptions to work efficiently - # Must point to a MongoDB replica set (local oplog or remote) - # For local MongoDB with replicaSet named 'rs0', use: - # export MONGO_OPLOG_URL=mongodb://127.0.0.1:27017/local?replicaSet=rs0 - # For production with credentials and remote MongoDB: - # export MONGO_OPLOG_URL=mongodb://:@:/local?authSource=admin&replicaSet=rsWekan - # Without this, Meteor falls back to polling which increases CPU usage and latency - #export MONGO_OPLOG_URL=mongodb://127.0.0.1:27017/local?replicaSet=rs0 - #----------------------------------------------------------------- # If port is 80, must change ROOT_URL to: http://YOUR-WEKAN-SERVER-IPv4-ADDRESS , like http://192.168.0.100 # If port is not 80, must change ROOT_URL to: http://YOUR-WEKAN-SERVER-IPv4-ADDRESS:YOUR-PORT-NUMBER , like http://192.168.0.100:2000 # If ROOT_URL is not correct, these do not work: translations, uploading attachments. diff --git a/tests/wekanCreator.import.test.js b/tests/wekanCreator.import.test.js deleted file mode 100644 index 55dc99539..000000000 --- a/tests/wekanCreator.import.test.js +++ /dev/null @@ -1,313 +0,0 @@ -/** - * Test: WekanCreator import with swimlane preservation - * - * Simulates exporting a board with swimlanes and importing it back, - * verifying that: - * 1. Swimlanes are correctly mapped from old IDs to new IDs - * 2. Cards reference the correct swimlane IDs after import - * 3. A default swimlane is created when no swimlanes exist - * 4. ID normalization (id → _id) works for all exported items - */ - -// Mock data: exported board with swimlanes and cards -const mockExportedBoard = { - _format: 'wekan-board-1.0.0', - _id: 'board1', - title: 'Test Board', - archived: false, - color: 'bgnone', - permission: 'private', - createdAt: new Date().toISOString(), - modifiedAt: new Date().toISOString(), - members: [ - { - userId: 'user1', - wekanId: 'user1', - isActive: true, - isAdmin: true, - }, - ], - labels: [], - swimlanes: [ - { - _id: 'swimlane1', - title: 'Swimlane 1', - archived: false, - sort: 0, - }, - { - _id: 'swimlane2', - title: 'Swimlane 2', - archived: false, - sort: 1, - }, - ], - lists: [ - { - _id: 'list1', - title: 'To Do', - archived: false, - sort: 0, - }, - { - _id: 'list2', - title: 'Done', - archived: false, - sort: 1, - }, - ], - cards: [ - { - _id: 'card1', - title: 'Card in swimlane 1', - archived: false, - swimlaneId: 'swimlane1', - listId: 'list1', - sort: 0, - description: 'Test card', - dateLastActivity: new Date().toISOString(), - labelIds: [], - }, - { - _id: 'card2', - title: 'Card in swimlane 2', - archived: false, - swimlaneId: 'swimlane2', - listId: 'list2', - sort: 0, - description: 'Another test card', - dateLastActivity: new Date().toISOString(), - labelIds: [], - }, - ], - comments: [], - activities: [ - { - activityType: 'createBoard', - createdAt: new Date().toISOString(), - userId: 'user1', - }, - ], - checklists: [], - checklistItems: [], - subtaskItems: [], - customFields: [], - rules: [], - triggers: [], - actions: [], - users: [ - { - _id: 'user1', - username: 'admin', - profile: { - fullname: 'Admin User', - }, - }, - ], -}; - -// Export format variation: using 'id' instead of '_id' -const mockExportedBoardWithIdField = { - ...mockExportedBoard, - swimlanes: [ - { - id: 'swimlane1', - title: 'Swimlane 1 (id variant)', - archived: false, - sort: 0, - }, - ], - lists: [ - { - id: 'list1', - title: 'To Do (id variant)', - archived: false, - sort: 0, - }, - ], - cards: [ - { - id: 'card1', - title: 'Card (id variant)', - archived: false, - swimlaneId: 'swimlane1', - listId: 'list1', - sort: 0, - description: 'Test card with id field', - dateLastActivity: new Date().toISOString(), - labelIds: [], - }, - ], -}; - -// Test: Verify id → _id normalization -function testIdNormalization() { - console.log('\n=== Test: ID Normalization (id → _id) ==='); - - // Simulate the normalization logic from WekanCreator constructor - const normalizeIds = arr => { - if (!arr) return; - arr.forEach(item => { - if (item && item.id && !item._id) { - item._id = item.id; - } - }); - }; - - const testData = { - lists: mockExportedBoardWithIdField.lists, - cards: mockExportedBoardWithIdField.cards, - swimlanes: mockExportedBoardWithIdField.swimlanes, - }; - - normalizeIds(testData.lists); - normalizeIds(testData.cards); - normalizeIds(testData.swimlanes); - - // Check results - if (testData.swimlanes[0]._id === 'swimlane1') { - console.log('✓ Swimlane: id → _id normalization created _id'); - } else { - console.log('✗ Swimlane: id → _id normalization FAILED'); - } - - if (testData.lists[0]._id === 'list1') { - console.log('✓ List: id → _id normalization created _id'); - } else { - console.log('✗ List: id → _id normalization FAILED'); - } - - if (testData.cards[0]._id === 'card1') { - console.log('✓ Card: id → _id normalization created _id'); - } else { - console.log('✗ Card: id → _id normalization FAILED'); - } -} - -// Test: Verify swimlane mapping during import -function testSwimlaneMapping() { - console.log('\n=== Test: Swimlane Mapping (export → import) ==='); - - // Simulate WekanCreator swimlane mapping - const swimlanes = {}; - const swimlaneIndexMap = {}; // Track old → new ID mappings - - // Simulate createSwimlanes: build mapping of old ID → new ID - mockExportedBoard.swimlanes.forEach((swimlane, index) => { - const oldId = swimlane._id; - const newId = `new_swimlane_${index}`; // Simulated new ID - swimlanes[oldId] = newId; - swimlaneIndexMap[oldId] = newId; - }); - - console.log(`✓ Created mapping for ${Object.keys(swimlanes).length} swimlanes:`); - Object.entries(swimlanes).forEach(([oldId, newId]) => { - console.log(` ${oldId} → ${newId}`); - }); - - // Simulate createCards: cards reference swimlanes using mapping - const cardSwimlaneCheck = mockExportedBoard.cards.every(card => { - const oldSwimlaneId = card.swimlaneId; - const newSwimlaneId = swimlanes[oldSwimlaneId]; - return newSwimlaneId !== undefined; - }); - - if (cardSwimlaneCheck) { - console.log('✓ All cards can be mapped to swimlanes'); - } else { - console.log('✗ Some cards have missing swimlane mappings'); - } -} - -// Test: Verify default swimlane creation when none exist -function testDefaultSwimlaneCreation() { - console.log('\n=== Test: Default Swimlane Creation ==='); - - const boardWithoutSwimlanes = { - ...mockExportedBoard, - swimlanes: [], - }; - - // Simulate the default swimlane logic from WekanCreator - let swimlanes = {}; - let defaultSwimlaneId = null; - - // If no swimlanes were provided, create a default - if (!swimlanes || Object.keys(swimlanes).length === 0) { - defaultSwimlaneId = 'new_default_swimlane'; - console.log('✓ Default swimlane created:', defaultSwimlaneId); - } - - // Verify cards without swimlane references use the default - const cardsWithoutSwimlane = boardWithoutSwimlanes.cards.filter(c => !c.swimlaneId); - if (cardsWithoutSwimlane.length > 0 && defaultSwimlaneId) { - console.log(`✓ ${cardsWithoutSwimlane.length} cards will use default swimlane`); - } else if (cardsWithoutSwimlane.length === 0) { - console.log('✓ No cards lacking swimlane (test data all have swimlaneId)'); - } -} - -// Test: Verify swimlane + card integrity after full import cycle -function testFullImportCycle() { - console.log('\n=== Test: Full Import Cycle ==='); - - // Step 1: Normalize IDs - const normalizeIds = arr => { - if (!arr) return; - arr.forEach(item => { - if (item && item.id && !item._id) { - item._id = item.id; - } - }); - }; - - const data = JSON.parse(JSON.stringify(mockExportedBoard)); // Deep copy - normalizeIds(data.swimlanes); - normalizeIds(data.lists); - normalizeIds(data.cards); - - // Step 2: Map swimlanes - const swimlaneMap = {}; - data.swimlanes.forEach((s, idx) => { - swimlaneMap[s._id] = `imported_swimlane_${idx}`; - }); - - // Step 3: Verify cards get mapped swimlane IDs - let unmappedCards = 0; - data.cards.forEach(card => { - if (card.swimlaneId && !swimlaneMap[card.swimlaneId]) { - unmappedCards++; - } - }); - - if (unmappedCards === 0) { - console.log('✓ All cards have valid swimlane references'); - } else { - console.log(`✗ ${unmappedCards} cards have unmapped swimlane references`); - } - - if (data.swimlanes.length > 0) { - console.log(`✓ ${data.swimlanes.length} swimlanes preserved in import`); - } - - if (data.cards.length > 0) { - console.log(`✓ ${data.cards.length} cards preserved in import`); - } -} - -// Run all tests -if (typeof describe === 'undefined') { - // Running in Node.js or standalone (not Mocha) - console.log('===================================='); - console.log('WekanCreator Import Tests'); - console.log('===================================='); - - testIdNormalization(); - testSwimlaneMapping(); - testDefaultSwimlaneCreation(); - testFullImportCycle(); - - console.log('\n===================================='); - console.log('Tests completed'); - console.log('====================================\n'); -}